import frappe
import time
from collections import defaultdict
from frappe import _
from ..utils.YT import YunExpressClient as YT
from ..utils.WDT import WMSWDTClient
from frappe.utils import get_url, cint
import requests
import xml.etree.ElementTree as ET
import json
from typing import Any, Dict, List, Optional
from xml.sax.saxutils import escape
from werkzeug.wrappers import Response
import requests

# 旺店通WMS配置常量---旺店通公司测试环境
#WDT_WAREHOUSE_CODE = "wms_lt2_flowaWDT"  # 旺店通仓库编码
#WDT_OWNER_CODE = "wms_lt2_flowaWDT"      # 旺店通货主编码

# 旺店通WMS配置常量---旺店通龙琪环境
WDT_WAREHOUSE_CODE = "LQ-WDT"  # 旺店通仓库编码
WDT_OWNER_CODE = "wms_lt2_flowa01WDT"      # 旺店通货主编码

yt = YT()
wdt = WMSWDTClient()

def callback_return(
    code: str,
    flag: str,
    message: str,
    extra: Optional[Dict[str, Any]] = None,
) -> Response:
    """Return an XML response following WDT specification."""

    extra = extra or {}

    parts: List[str] = ["<response>"]
    parts.append(f"<flag>{escape(str(flag))}</flag>")
    parts.append(f"<code>{escape(str(code))}</code>")
    parts.append(f"<message>{escape(message or '')}</message>")
    for key, value in extra.items():
        if value is None:
            continue
        parts.append(f"<{key}>{escape(str(value))}</{key}>")
    parts.append("</response>")

    xml_body = "".join(parts)

    # store metadata for logging/debugging
    frappe.response.update({
        "type": "text",
        "flag": flag,
        "code": code,
        "message": message,
    })

    return Response(xml_body.encode("utf-8"), content_type="text/xml; charset=utf-8")


# ========================================
# Shopify API接口
# ========================================
@frappe.whitelist(allow_guest=True)
def do_shopify_request(query_params):
    try:
        if isinstance(query_params, str):
            import json
            query_params = json.loads(query_params)
        # 验证必需参数 - 只有国家是必需的
        if not query_params.get('shopname'):
            return {
                "success": False,
                "message": "缺少必需参数: shopname",
                "data": None
            }

        # 提取参数
        shopname = query_params.get('shopname')
        querydata = query_params.get('querydata')
        variables = query_params.get('variables')

        if not variables:
            variables = {}

        user = frappe.session.user
        shops = frappe.get_all(
            'usershop',
            fields=["name", "username", "shopname", "shoptoken", "shopurl", "remark", "ext"],
            filters={"name": shopname, "username": user}
        )
        if not shops:
            return {
                "success": False,
                "user": user,
                "message": f"shopify店铺不存在: {shopname}",
                "data": None
            }
        shop = shops[0]
        url = f"{shop.shopurl}"
        payload = {"query": querydata, "variables": variables}

        headers = {
            "Content-Type": "application/json",
            "X-Shopify-Access-Token": shop.shoptoken
        }
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code != 200:
            return {
                "success": False,
                "message": f"shopify获取数据失败: {response.status_code} {response.text}",
                "data": None
            }
        data = response.json()
        return {
            "success": True,
            "user": user,
            "message": "ok",
            "data": data
        }
    except Exception as e:
        frappe.logger().error(f"shopify获取数据失败: {str(e)}")
        return {
            "success": False,
            "message": f"获取失败: {str(e)}",
            "data": None
        }



def xml_to_dict(element):
    """
    将XML元素转换为字典
    修复：正确处理根元素结构
    """
    result = {}

    # 处理元素的子元素
    for child in element:
        if len(child) > 0:
            # 如果子元素有子元素，递归处理
            child_data = xml_to_dict(child)
            # 如果该键已存在，转换为列表
            if child.tag in result:
                if not isinstance(result[child.tag], list):
                    result[child.tag] = [result[child.tag]]
                result[child.tag].append(child_data)
            else:
                result[child.tag] = child_data
        else:
            # 叶子节点，直接获取文本内容
            # 如果该键已存在，转换为列表
            if child.tag in result:
                if not isinstance(result[child.tag], list):
                    result[child.tag] = [result[child.tag]]
                result[child.tag].append(child.text or '')
            else:
                result[child.tag] = child.text or ''

    return result


@frappe.whitelist()
def has_app_permission():
    """
    检查用户是否有权限访问应用
    """
    # 这里可以添加具体的权限检查逻辑
    # 目前允许所有登录用户访问
    return True


@frappe.whitelist()
def get_test_data():
    """
    测试页面的数据接口
    返回测试数据给前端展示
    """
    try:
        # 模拟从数据库获取数据，这里返回一些测试数据
        test_data = [
            {
                "id": 1,
                "name": "用户测试数据 1",
                "description": "这是第一条测试数据",
                "status": "激活",
                "created_date": "2024-01-01",
                "value": 100
            },
            {
                "id": 2,
                "name": "用户测试数据 2",
                "description": "这是第二条测试数据",
                "status": "待审核",
                "created_date": "2024-01-02",
                "value": 200
            },
            {
                "id": 3,
                "name": "用户测试数据 3",
                "description": "这是第三条测试数据",
                "status": "激活",
                "created_date": "2024-01-03",
                "value": 300
            }
        ]

        # 也可以从实际数据库查询数据，例如：
        # test_data = frappe.get_all("DocType Name",
        #                          fields=["name", "description", "status"],
        #                          limit=10)

        return {
            "success": True,
            "data": yt.get_country_code(),
            "message": _("数据获取成功")
        }

    except Exception as e:
        frappe.log_error(frappe.get_traceback(), "获取测试数据失败")
        return {
            "success": False,
            "data": [],
            "message": _("获取数据失败: {0}").format(str(e))
        }


@frappe.whitelist()
def flowa_list_sales_orders(
    order_no: str | None = None,
    sku: str | None = None,
    country: str | None = None,
    status: str | None = None,
    item_group: str | None = None,
    has_stock: str | None = None,
    page: int = 1,
    page_size: int = 20,
):
    """Provide paginated sales orders with filter support for Flowa OMS UI."""

    if frappe.session.user == "Guest":
        frappe.throw(_("Please log in to view orders."), frappe.PermissionError)

    page = max(cint(page or 1), 1)
    page_size = min(max(cint(page_size or 20), 1), 200)

    filters: Dict[str, Any] = {}
    allowed_names: set[str] | None = None

    def _merge_allowed(raw_names):
        nonlocal allowed_names
        names = {
            row[0] if isinstance(row, (list, tuple)) else row
            for row in (raw_names or [])
            if row
        }
        if allowed_names is None:
            allowed_names = names
        else:
            allowed_names &= names

    if order_no:
        like_value = f"%{order_no.strip()}%"
        names = frappe.get_all(
            "Sales Order", filters={"name": ["like", like_value]}, pluck="name"
        )
        _merge_allowed(names)

    if sku:
        like_value = f"%{sku.strip()}%"
        names = frappe.db.sql(
            """
            SELECT DISTINCT parent
            FROM `tabSales Order Item`
            WHERE item_code LIKE %s
            """,
            (like_value,),
        )
        _merge_allowed(names)

    if item_group:
        names = frappe.db.sql(
            """
            SELECT DISTINCT soi.parent
            FROM `tabSales Order Item` soi
            INNER JOIN `tabItem` i ON soi.item_code = i.name
            WHERE i.item_group = %s
            """,
            (item_group,),
        )
        _merge_allowed(names)

    if has_stock in {"in_stock", "no_stock"}:
        comparison = ">" if has_stock == "in_stock" else "<="
        names = frappe.db.sql(
            f"""
            SELECT soi.parent
            FROM `tabSales Order Item` soi
            LEFT JOIN (
                SELECT item_code, SUM(actual_qty) AS qty
                FROM `tabBin`
                GROUP BY item_code
            ) stock ON stock.item_code = soi.item_code
            GROUP BY soi.parent
            HAVING MIN(COALESCE(stock.qty, 0)) {comparison} 0
            """
        )
        _merge_allowed(names)

    if allowed_names is not None:
        if not allowed_names:
            return {
                "data": [],
                "total": 0,
                "page": page,
                "page_size": page_size,
            }
        filters["name"] = ["in", list(allowed_names)]

    if country:
        # 目的国家：使用 Sales Order 上的自定义字段 custom_country
        filters["custom_country"] = country

    if status:
        filters["status"] = status

    total = frappe.db.count("Sales Order", filters=filters)
    if not total:
        return {
            "data": [],
            "total": 0,
            "page": page,
            "page_size": page_size,
        }

    start = (page - 1) * page_size
    orders = frappe.get_all(
        "Sales Order",
        filters=filters,
        fields=[
            "name",
            "transaction_date",
            "delivery_date",
            "status",
            "customer_name",
            "total_qty",
            "grand_total",
            "currency",
            # 自定义目的国家字段
            "custom_country",
            "shipping_address_country",
            "modified",
        ],
        order_by="transaction_date desc, modified desc",
        start=start,
        page_length=page_size,
    )

    if not orders:
        return {
            "data": [],
            "total": total,
            "page": page,
            "page_size": page_size,
        }

    order_names = [order["name"] for order in orders]

    order_items = frappe.get_all(
        "Sales Order Item",
        filters={"parent": ["in", order_names]},
        fields=["parent", "item_code", "item_name", "qty", "uom", "warehouse"],
        order_by="idx asc",
    )

    item_codes = {row["item_code"] for row in order_items if row.get("item_code")}

    item_groups_map: Dict[str, str] = {}
    if item_codes:
        for row in frappe.get_all(
            "Item", filters={"name": ["in", list(item_codes)]}, fields=["name", "item_group"]
        ):
            item_groups_map[row["name"]] = row.get("item_group")

    stock_map: Dict[str, float] = {}
    if item_codes:
        stock_rows = frappe.db.sql(
            """
            SELECT item_code, SUM(actual_qty) AS qty
            FROM `tabBin`
            WHERE item_code IN %(codes)s
            GROUP BY item_code
            """,
            {"codes": tuple(item_codes)},
            as_dict=True,
        )
        for row in stock_rows:
            stock_map[row.get("item_code")] = row.get("qty") or 0

    items_by_order: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
    for row in order_items:
        item_code = row.get("item_code")
        available_qty = stock_map.get(item_code, 0)
        items_by_order[row["parent"]].append(
            {
                "item_code": item_code,
                "item_name": row.get("item_name"),
                "qty": row.get("qty"),
                "uom": row.get("uom"),
                "warehouse": row.get("warehouse"),
                "item_group": item_groups_map.get(item_code, ""),
                "available_qty": available_qty,
            }
        )

    response_orders: List[Dict[str, Any]] = []
    for order in orders:
        order_items_list = items_by_order.get(order["name"], [])
        has_stock_flag = bool(order_items_list) and all(
            (item.get("available_qty") or 0) > 0 for item in order_items_list
        )
        response_orders.append(
            {
                "name": order.get("name"),
                "status": order.get("status"),
                "status_display": _(order.get("status")) if order.get("status") else "",
                "transaction_date": order.get("transaction_date"),
                "delivery_date": order.get("delivery_date"),
                "customer_name": order.get("customer_name"),
                "total_qty": order.get("total_qty"),
                "grand_total": float(order.get("grand_total") or 0),
                "currency": order.get("currency"),
                # 优先使用 custom_country
                "country": order.get("custom_country")
                or order.get("shipping_address_country"),
                "has_stock": has_stock_flag,
                "items": order_items_list,
            }
        )

    return {
        "data": response_orders,
        "total": total,
        "page": page,
        "page_size": page_size,
    }


@frappe.whitelist()
def get_dashboard_stats():
    """
    获取仪表板统计数据
    """
    try:
        stats = {
            "total_records": 3,
            "active_records": 2,
            "pending_records": 1,
            "total_value": 600
        }

        return {
            "success": True,
            "stats": stats,
            "message": _("统计数据获取成功")
        }

    except Exception as e:
        frappe.log_error(frappe.get_traceback(), "获取统计数据失败")
        return {
            "success": False,
            "stats": {},
            "message": _("获取统计数据失败: {0}").format(str(e))
        }


@frappe.whitelist()
def cancel_sales_order_in_wdt(sales_order_name, cancel_reason):
    """
    取消销售订单在旺店通WMS中的对应单据

    Args:
        sales_order_name (str): 销售订单名称
        cancel_reason (str): 取消原因（必填）

    Returns:
        dict: 操作结果
    """
    try:
        # 验证取消原因
        if not cancel_reason or not cancel_reason.strip():
            return {
                "success": False,
                "message": "取消原因不能为空"
            }
        
        # 获取销售订单信息
        sales_order = frappe.get_doc("Sales Order", sales_order_name)

        # 检查是否有云途订单号，如果有则先撤销云途订单
        yt_waybill_number = None
        if hasattr(sales_order, "custom_delivery_order_no") and sales_order.custom_delivery_order_no:
            yt_waybill_number = sales_order.custom_delivery_order_no
            frappe.logger().info(f"发现云途订单号: {yt_waybill_number}，开始撤销云途订单")

            # 撤销云途订单
            yt_cancel_result = cancel_yunexpress_order(yt_waybill_number)
            if not yt_cancel_result.get("success"):
                frappe.logger().error(f"撤销云途订单失败: {yt_cancel_result.get('message')}")
                # 云途撤销失败不影响旺店通取消，继续执行
            else:
                frappe.logger().info(f"云途订单撤销成功: {yt_waybill_number}")

        # 构建取消数据
        cancel_data = {
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 固定仓库编码
            "orderCode": sales_order_name,
            "orderType": "JYCK",  # 一般交易出库单
            "cancelReason": cancel_reason
        }

        # 调用旺店通取消接口
        result = wdt.wdt_wms_order_cancel(cancel_data)

        if result and result.get("code") == "0":
            # 构建取消原因，包含OMS订单撤销信息
            cancel_comment = f"订单取消原因: {cancel_reason}"
            if yt_waybill_number:
                if yt_cancel_result and yt_cancel_result.get("success"):
                    cancel_comment += f"；OMS订单 {yt_waybill_number} 已撤销"
                else:
                    cancel_comment += f"；OMS订单 {yt_waybill_number} 撤销失败"
            
            # 若云途撤销成功，清理相关单据字段，避免后续误用
            if yt_waybill_number and yt_cancel_result and yt_cancel_result.get("success"):
                if sales_order.meta.has_field("custom_delivery_order_no"):
                    sales_order.db_set("custom_delivery_order_no", "", update_modified=False)
                if sales_order.meta.has_field("custom_delivery_no"):
                    sales_order.db_set("custom_delivery_no", "", update_modified=False)
                if sales_order.meta.has_field("custom_label_url"):
                    sales_order.db_set("custom_label_url", "", update_modified=False)
                if sales_order.meta.has_field("custom_yt_ret_data"):
                    sales_order.db_set("custom_yt_ret_data", "", update_modified=False)
            
            # 添加取消注释并取消订单（cancel 会持久化状态，无需再 save）
            sales_order.add_comment("Comment", cancel_comment)
            sales_order.cancel()

            # 同步取消 Shopify 履约（忽略失败但记录日志）
            try:
                frappe.call(
                    "upsystem.upsystem.api_orders_import_shopify.cancel_shopify_fulfillment",
                    sales_order_name=sales_order_name,
                )
            except Exception:
                frappe.logger().error(
                    f"[Shopify Fulfillment] 取消订单后取消履约失败: {frappe.get_traceback()}"
                )

            return {
                "success": True,
                "message": "订单取消成功",
                "data": {
                    "wdt_result": result,
                    "yt_canceled": yt_waybill_number is not None,
                    "yt_waybill_number": yt_waybill_number,
                    "yt_cancel_result": yt_cancel_result if yt_waybill_number else None
                }
            }
        else:
            # 检查是否是"销售订单不存在"的错误
            wdt_message = result.get("message", "未知错误") if result else "接口调用失败"
            wdt_code = result.get("code", "unknown") if result else "no_response"
            wdt_flag = result.get("flag", "unknown") if result else "no_response"
            
            # 如果OMS返回"销售订单不存在"，说明订单没有同步到OMS，这是正常情况
            if "销售订单不存在" in wdt_message or "不存在" in wdt_message:
                # 构建取消原因，包含OMS订单撤销信息
                cancel_comment = f"订单取消原因: {cancel_reason}"
                if yt_waybill_number:
                    if yt_cancel_result and yt_cancel_result.get("success"):
                        cancel_comment += f"；OMS订单 {yt_waybill_number} 已撤销"
                    else:
                        cancel_comment += f"；OMS订单 {yt_waybill_number} 撤销失败"
                cancel_comment += f"；OMS订单不存在（未同步）"
                
                # 若云途撤销成功，清理相关字段
                if yt_waybill_number and yt_cancel_result and yt_cancel_result.get("success"):
                    if sales_order.meta.has_field("custom_delivery_order_no"):
                        sales_order.db_set("custom_delivery_order_no", "", update_modified=False)
                    if sales_order.meta.has_field("custom_delivery_no"):
                        sales_order.db_set("custom_delivery_no", "", update_modified=False)
                    if sales_order.meta.has_field("custom_label_url"):
                        sales_order.db_set("custom_label_url", "", update_modified=False)
                    if sales_order.meta.has_field("custom_yt_ret_data"):
                        sales_order.db_set("custom_yt_ret_data", "", update_modified=False)
                
                # 添加取消注释并取消订单（cancel 会持久化状态，无需再 save）
                sales_order.add_comment("Comment", cancel_comment)
                sales_order.cancel()

                # 同步取消 Shopify 履约（忽略失败但记录日志）
                try:
                    frappe.call(
                        "upsystem.upsystem.api_orders_import_shopify.cancel_shopify_fulfillment",
                        sales_order_name=sales_order_name,
                    )
                except Exception:
                    frappe.logger().error(
                        f"[Shopify Fulfillment] 取消订单后取消履约失败: {frappe.get_traceback()}"
                    )

                return {
                    "success": True,
                    "message": "订单取消成功（旺店通订单不存在，未同步）",
                    "data": {
                        "wdt_result": result,
                        "yt_canceled": yt_waybill_number is not None,
                        "yt_waybill_number": yt_waybill_number,
                        "yt_cancel_result": yt_cancel_result if yt_waybill_number else None,
                        "wdt_not_synced": True
                    }
                }
            else:
                # 其他OMS错误，正常报错
                # 构建错误信息，包含OMS订单撤销信息
                error_message = f"OMS取消订单失败: {wdt_message}"
                if yt_waybill_number:
                    if yt_cancel_result and yt_cancel_result.get("success"):
                        error_message += f"；OMS订单 {yt_waybill_number} 已撤销"
                    else:
                        error_message += f"；OMS订单 {yt_waybill_number} 撤销失败"

                return {
                    "success": False,
                    "message": error_message,
                    "wdt_code": wdt_code,
                    "wdt_flag": wdt_flag,
                    "wdt_message": wdt_message,
                    "data": result,
                    "yt_canceled": yt_waybill_number is not None,
                    "yt_waybill_number": yt_waybill_number,
                    "yt_cancel_result": yt_cancel_result if yt_waybill_number else None
                }

    except Exception as e:
        frappe.logger().error(f"取消销售订单失败: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return {
            "success": False,
            "message": f"取消订单失败: {str(e)}"
        }


@frappe.whitelist()
def copy_sales_order(sales_order_name, new_delivery_date=None):
    """
    复制销售订单，创建新的订单
    
    Args:
        sales_order_name (str): 原销售订单名称
        new_delivery_date (str): 新交付日期（可选，格式：YYYY-MM-DD）
        
    Returns:
        dict: 操作结果
    """
    try:
        # 获取原销售订单
        original_order = frappe.get_doc("Sales Order", sales_order_name)
        
        # 创建新订单
        new_order = frappe.copy_doc(original_order)
        
        # 清空系统字段
        new_order.name = None
        new_order.docstatus = 0
        new_order.status = "Draft"
        new_order.creation = None
        new_order.modified = None
        new_order.modified_by = None
        new_order.owner = None
        new_order.amended_from = None
        new_order.amended_from_docname = None
        
        # 更新交付日期
        if new_delivery_date:
            new_order.delivery_date = new_delivery_date
        else:
            # 如果没有指定新日期，设置为今天
            new_order.delivery_date = frappe.utils.today()
        
        # 更新订单标题和备注
        new_order.title = f"复制自 {sales_order_name}"
        new_order.remarks = f"此订单复制自 {sales_order_name}，原订单状态：{original_order.status}"
        
        # 清空物流相关字段
        if hasattr(new_order, "custom_delivery_order_no"):
            new_order.custom_delivery_order_no = None
        if hasattr(new_order, "custom_warehouse_order_no"):
            new_order.custom_warehouse_order_no = None
        if hasattr(new_order, "custom_label_url"):
            new_order.custom_label_url = None
        if hasattr(new_order, "custom_shopify_order_id"):
            new_order.custom_shopify_order_id = None
        if hasattr(new_order, "custom_yt_ret_data"):
            new_order.custom_yt_ret_data = None
        if hasattr(new_order, "custom_wdt_callback_data"):
            new_order.custom_wdt_callback_data = None
        
        # 保存新订单
        new_order.insert()
        
        # 添加复制记录注释
        new_order.add_comment("Comment", f"订单复制自 {sales_order_name}，原订单创建时间：{original_order.creation}")
        
        return {
            "success": True,
            "message": "订单复制成功",
            "data": {
                "new_order_name": new_order.name,
                "original_order": sales_order_name,
                "customer": new_order.customer,
                "delivery_date": new_order.delivery_date,
                "items_count": len(new_order.items)
            }
        }
        
    except Exception as e:
        frappe.logger().error(f"复制订单失败: {str(e)}")
        return {
            "success": False,
            "message": f"复制订单失败: {str(e)}"
        }


@frappe.whitelist()
def query_inventory_by_item_code(item_code):
    """
    根据商品编码查询库存
    """
    try:
        if not item_code:
            return {"success": False, "message": "商品编码不能为空"}

        wdt = WMSWDTClient()

        # 构建查询条件
        criteria_list = [{
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 固定仓库编码
            "ownerCode": WDT_OWNER_CODE,  # 固定货主编码
            "itemCode": item_code,
            "inventoryType": "ZP",
            "remark": f"商品库存查询: {item_code}"
        }]

        query_data = {
            "criteriaList": criteria_list,
            "remark": f"商品库存查询: {item_code}"
        }

        frappe.logger().error(f"商品库存查询参数: {query_data}")

        result = wdt.wdt_wms_inventory_query(query_data)
        frappe.logger().error(f"旺店通库存查询响应: {result}")

        return {
            "success": True,
            "message": "查询成功",
            "data": result
        }

    except Exception as e:
        frappe.logger().error(f"查询商品库存失败: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return {"success": False, "message": f"查询库存失败: {str(e)}"}


@frappe.whitelist()
def query_position_by_item_code():
    """
    查询所有商品的库位占用情况
    """
    try:
        wdt = WMSWDTClient()

        # 构建请求参数
        request_params = {
            "owner_no": WDT_OWNER_CODE,  # 固定货主编码
            "warehouse_no": WDT_WAREHOUSE_CODE,  # 固定仓库编码
            "defect": 0,  # 查询正品
            "page_size": 100,  # 每页记录数
            "page_no": 0  # 从第0页开始
        }

        frappe.logger().error(f"库位查询请求参数: {request_params}")

        # 使用stock_total_query方法，设置is_position=1获取货位信息
        result = wdt.stock_total_query(
            owner_no=request_params["owner_no"],
            warehouse_no=request_params["warehouse_no"],
            is_position=1,  # 获取货位信息
            defect=request_params["defect"],
            page_size=request_params["page_size"],
            page_no=request_params["page_no"]
        )

        frappe.logger().error(f"旺店通库位查询响应: {result}")

        # 检查响应结果
        if result and result.get("flag") == "success":
            content_count = len(result.get("content", [])) if result.get("content") else 0
            frappe.logger().error(f"库位查询成功，返回 {content_count} 条记录")
        else:
            frappe.logger().error(f"库位查询失败，响应标志: {result.get('flag') if result else 'None'}")

        return {
            "success": True,
            "message": "查询成功",
            "data": result
        }

    except Exception as e:
        frappe.logger().error(f"查询商品库位失败: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return {"success": False, "message": f"查询库位失败: {str(e)}"}



@frappe.whitelist(allow_guest=True)
def wdt_entryorder_confirm_callback():
    try:
        logger = frappe.logger()
        # 获取请求数据
        if frappe.request.method == 'POST':
            raw_data = frappe.request.get_data(as_text=True)
            # 记录原始请求体，便于排查（仅截断预览，避免日志过大）
            try:
                ct = frappe.request.content_type
                raw_preview = (raw_data or "")[:4000]
                form_view = dict(frappe.form_dict) if hasattr(frappe, "form_dict") else {}
                frappe.logger().error(f"[WDT_CALLBACK_IN][DELIVERY_CONFIRM] ct={ct} form={form_view} raw_preview={raw_preview}")
            except Exception:
                pass

            if frappe.request.content_type and 'application/json' in frappe.request.content_type:
                # JSON格式数据
                callback_data = frappe.request.get_json()
            elif (frappe.request.content_type and ('text/xml' in frappe.request.content_type or 'application/xml' in frappe.request.content_type)) or (raw_data and raw_data.strip().startswith('<')):
                # XML格式数据 - 需要解析XML
                import xml.etree.ElementTree as ET
                xml_data = raw_data

                try:
                    # 处理转义的引号
                    xml_data = xml_data.replace('\\"', '"')
                    # 解析XML
                    root = ET.fromstring(xml_data)
                    callback_data = xml_to_dict(root)
                except Exception as e:
                    frappe.logger().error(f"XML解析失败: {str(e)}")
                    callback_data = frappe.form_dict
            else:
                # 表单数据
                callback_data = frappe.form_dict
        else:
            callback_data = frappe.form_dict

        # 打印解析后的回调数据，方便核查“写库失败”的原因
        try:
            import json as _json
            parsed = _json.dumps(callback_data, ensure_ascii=False) if isinstance(callback_data, dict) else str(callback_data)
            frappe.logger().error(f"[WDT_CALLBACK_PARSED][DELIVERY_CONFIRM] preview={parsed[:4000]}")
        except Exception:
            pass

        # 验证必需字段
        if not callback_data:
            return callback_return("1", "failure", "回调数据不能为空")

        # 处理XML解析后的数据结构
        # XML解析后，根元素<request>的子元素直接成为callback_data的键
        if 'entryOrder' in callback_data and 'orderLines' in callback_data:
            # XML格式：直接获取（因为xml_to_dict处理了根元素）
            entry_order = callback_data.get('entryOrder', {})
            order_lines_data = callback_data.get('orderLines', {})
        elif 'request' in callback_data:
            # 如果数据被包装在request节点下
            entry_order = callback_data['request'].get('entryOrder', {})
            order_lines_data = callback_data['request'].get('orderLines', {})
        else:
            # JSON格式或其他格式：直接获取
            entry_order = callback_data.get('entryOrder', {})
            order_lines_data = callback_data.get('orderLines', {})

        # 处理XML解析后的数据结构
        if isinstance(order_lines_data, dict) and 'orderLine' in order_lines_data:
            # XML解析后的结构：orderLines.orderLine 可能是单个对象或数组
            order_line_data = order_lines_data['orderLine']
            if isinstance(order_line_data, list):
                # 多个orderLine的情况
                order_lines = order_line_data
            else:
                # 单个orderLine的情况，转换为数组
                order_lines = [order_line_data]
        else:
            # JSON格式或其他格式
            order_lines = order_lines_data if isinstance(order_lines_data, list) else []

        # 验证orderLines数据
        if not order_lines:
            return callback_return("1", "failure", "缺少订单行信息")

        if not entry_order:
            return callback_return("1", "failure", "缺少入库单信息")

        # 获取入库单号 - 对应文档中的 entryOrderCode
        entry_order_code = entry_order.get('entryOrderCode')
        if not entry_order_code:
            return callback_return("1", "failure", "缺少入库单号")

        # 获取仓储系统入库单ID - 对应文档中的 entryOrderId（必填）
        entry_order_id = entry_order.get('entryOrderId', '')
        if not entry_order_id:
            return callback_return("1", "failure", "缺少仓储系统入库单ID")

        # 获取仓库编码 - 对应文档中的 warehouseCode（必填）
        warehouse_code = entry_order.get('warehouseCode', '')
        if not warehouse_code:
            return callback_return("1", "failure", "缺少仓库编码")

        # 获取入库单类型 - 对应文档中的 entryOrderType
        entry_order_type = entry_order.get('entryOrderType', '')

        # 获取外部业务编码 - 对应文档中的 outBizCode（必填）
        out_biz_code = entry_order.get('outBizCode', '')
        if not out_biz_code:
            return callback_return("1", "failure", "缺少外部业务编码")

        # 获取确认类型 - 对应文档中的 confirmType（必填）
        confirm_type = entry_order.get('confirmType', 0)
        if confirm_type is None or confirm_type == '':
            return callback_return("1", "failure", "缺少确认类型")

        # 获取扩展属性中的物料申请单号
        extend_props = entry_order.get('extendProps', {})
        material_request_code = extend_props.get('material_request_code', '')
        prop1 = extend_props.get('prop1', '')
        prop2 = extend_props.get('prop2', '')

        # 确定物料申请单号（优先使用专门的字段，其次使用prop1，最后使用entryOrderCode）
        if material_request_code:
            actual_material_request_code = material_request_code
        elif prop1 and prop2 == "MATERIAL_REQUEST":
            actual_material_request_code = prop1
        else:
            # 如果都没有，使用entryOrderCode（因为创建时就是物料申请单号）
            actual_material_request_code = entry_order_code

        # 检查入库单状态 - 对应文档中的 status
        status = entry_order.get('status')
        if status not in ['PARTFULFILLED', 'FULFILLED']:
            return callback_return("1", "failure", f"无效的入库单状态: {status}")

        # 获取操作时间 - 对应文档中的 operateTime（必填）
        operate_time = entry_order.get('operateTime', '')
        if not operate_time:
            return callback_return("1", "failure", "缺少操作时间")

        # 获取备注信息 - 对应文档中的 remark
        remark = entry_order.get('remark', '')

        # 查找对应的物料申请单
        try:
            # 检查是否已经处理过这个入库单，避免重复处理
            existing_stock_entries = frappe.get_all(
                "Stock Entry",
                filters={
                    "material_request": actual_material_request_code,
                    "stock_entry_type": "Material Receipt",
                    "docstatus": ["!=", 2]  # 不是已取消的
                },
                fields=["name", "docstatus"]
            )

            if existing_stock_entries:
                handled_entries = [se.name for se in existing_stock_entries]
                handled_entries_str = ", ".join(handled_entries)

                frappe.logger().info(
                    f"物料申请单 {actual_material_request_code} 已存在入库单: {handled_entries_str}"
                )
                logger.info(
                    f"[WDT_ENTRY_CONFIRM] 检测到重复处理，返回成功: {handled_entries_str}"
                )

                return callback_return(
                    code="0",
                    flag="success",
                    message=(
                        f"物料申请单 {actual_material_request_code} 已处理过，避免重复处理（已存在入库单: {handled_entries_str}）"
                    ),
                )

            material_requests = frappe.get_all(
                "Material Request",
                filters={"name": actual_material_request_code},
                fields=["name", "docstatus", "status"]
            )

            if material_requests:
                material_request = frappe.get_doc("Material Request", actual_material_request_code)

                # 自动创建物料入库单（Stock Entry）
                try:
                    stock_entry = create_material_receipt_from_callback(
                        material_request,
                        order_lines,
                        entry_order_id,
                        warehouse_code,
                        operate_time,
                        entry_order_type,
                        out_biz_code,
                        confirm_type,
                        remark,
                        status
                    )
                    if not stock_entry:
                        return callback_return("1", "failure", "物料入库单创建失败")
                except Exception as e:
                    frappe.logger().error(f"创建物料入库单失败: {str(e)}")
                    return callback_return("1", "failure", f"创建物料入库单失败: {str(e)}")

                # 提交数据库事务
                frappe.db.commit()

                return callback_return("0", "success", "入库确认处理成功")
            else:
                # 即使找不到物料申请单，也返回成功，避免旺店通重复回调
                logger.info(
                    f"[WDT_ENTRY_CONFIRM] 未找到物料申请单 actual_material_request_code={actual_material_request_code}"
                )
                return callback_return(
                    code="0",
                    flag="success",
                    message=(
                        f"回调处理成功（未找到对应物料申请单: {actual_material_request_code}，跳过业务处理）"
                    ),
                )
        except Exception as e:
            frappe.logger().error(f"处理入库确认失败: {str(e)}")
            frappe.db.rollback()
            return callback_return("1", "failure", f"处理入库确认失败: {str(e)}")

    except Exception as e:
        frappe.logger().error(f"旺店通入库回调处理异常: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return callback_return("1", "failure", f"回调处理异常: {str(e)}")

@frappe.whitelist(allow_guest=True)
def wdt_test():
    """测试 WDT 回调接口是否可访问"""
    try:
        return {
            "message": "WDT 回调接口测试成功",
            "status": "ok",
            "timestamp": frappe.utils.now(),
            "api_path": "/api/method/wdt_entryorder_confirm_callback",
            "user": frappe.session.user if hasattr(frappe, 'session') else "guest"
        }
    except Exception as e:
        return {
            "message": f"测试失败: {str(e)}",
            "status": "error",
            "timestamp": frappe.utils.now()
        }

@frappe.whitelist(allow_guest=True)
def simple_test():
    """简单的测试接口"""
    return {"message": "Hello World", "status": "success"}

@frappe.whitelist(allow_guest=True)
def wdt_callback_test():
    """旺店通回调测试接口"""
    return {
        "flag": "success",
        "code": "0",
        "message": "旺店通回调接口测试成功",
        "timestamp": frappe.utils.now()
    }

@frappe.whitelist(allow_guest=True)
def wdt_unified_callback():
    """
    旺店通统一回调接口
    根据method参数分发到不同的处理方法
    """
    try:
        # 获取method参数
        method = frappe.form_dict.get('method')

        if not method:
            return callback_return("1", "failure", "缺少method参数")

        # 根据method参数分发到不同的处理方法
        if method == "WDT_WMS_ENTRYORDER_CONFIRM":
            # 入库单确认回调
            return wdt_entryorder_confirm_callback()
        elif method == "WDT_WMS_DELIVERYORDER_CONFIRM":
            # 发货单确认回调
            return wdt_deliveryorder_confirm_callback()
        elif method == "WDT_WMS_STOCKOUT_CONFIRM":
            # 出库单确认回调
            return wdt_stockout_confirm_callback()
        elif method == "WDT_WMS_RETURNORDER_CONFIRM":
            # 退货入库单确认回调
            return wdt_returnorder_confirm_callback()
        else:
            return callback_return("1", "failure", f"不支持的method参数: {method}")

    except Exception as e:
        frappe.logger().error(f"旺店通统一回调处理异常: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return callback_return("1", "failure", f"回调处理异常: {str(e)}")

@frappe.whitelist(allow_guest=True)
def wdt_stockout_confirm_callback():
    """
    旺店通出库单确认回调接口
    接口名称：WDT_WMS_STOCKOUT_CONFIRM
    调用方向：WMS→ERP
    """
    try:
        frappe.logger().info("wdt_stockout_confirm_callback 被调用")
        # TODO: 实现出库单确认回调逻辑
        return callback_return("0", "success", "出库单确认回调处理成功")
    except Exception as e:
        frappe.logger().error(f"出库单确认回调处理异常: {str(e)}")
        return callback_return("1", "failure", f"出库单确认回调处理异常: {str(e)}")

@frappe.whitelist(allow_guest=True)
def wdt_returnorder_confirm_callback():
    """
    旺店通退货入库单确认回调接口
    接口名称：WDT_WMS_RETURNORDER_CONFIRM
    调用方向：WMS→ERP
    """
    try:
        frappe.logger().info("wdt_returnorder_confirm_callback 被调用")
        # TODO: 实现退货入库单确认回调逻辑
        return callback_return("0", "success", "退货入库单确认回调处理成功")
    except Exception as e:
        frappe.logger().error(f"退货入库单确认回调处理异常: {str(e)}")
        return callback_return("1", "failure", f"退货入库单确认回调处理异常: {str(e)}")

@frappe.whitelist(allow_guest=True)
def wdt_deliveryorder_confirm_callback():
    """
    旺店通发货单确认回调接口
    接口名称：WDT_WMS_DELIVERYORDER_CONFIRM
    调用方向：WMS→ERP
    """
    try:
        # 获取请求数据
        if frappe.request.method == 'POST':
            raw_data = frappe.request.get_data(as_text=True)

            if frappe.request.content_type and 'application/json' in frappe.request.content_type:
                # JSON格式数据
                callback_data = frappe.request.get_json()
            elif (frappe.request.content_type and ('text/xml' in frappe.request.content_type or 'application/xml' in frappe.request.content_type)) or (raw_data and raw_data.strip().startswith('<')):
                # XML格式数据 - 需要解析XML
                import xml.etree.ElementTree as ET
                xml_data = raw_data

                try:
                    # 处理转义的引号
                    xml_data = xml_data.replace('\\"', '"')
                    # 解析XML
                    root = ET.fromstring(xml_data)
                    callback_data = xml_to_dict(root)
                except Exception as e:
                    frappe.logger().error(f"XML解析失败: {str(e)}")
                    callback_data = frappe.form_dict
            else:
                # 表单数据
                callback_data = frappe.form_dict
        else:
            callback_data = frappe.form_dict

        # 验证必需字段
        if not callback_data:
            return callback_return("1", "failure", "回调数据不能为空")

        # 处理XML解析后的数据结构
        if 'deliveryOrder' in callback_data and 'orderLines' in callback_data:
            # XML格式：直接获取
            delivery_order = callback_data.get('deliveryOrder', {})
            order_lines_data = callback_data.get('orderLines', {})
            packages_data = callback_data.get('packages', {})
        elif 'request' in callback_data:
            # 如果数据被包装在request节点下
            delivery_order = callback_data['request'].get('deliveryOrder', {})
            order_lines_data = callback_data['request'].get('orderLines', {})
            packages_data = callback_data['request'].get('packages', {})
        else:
            # JSON格式或其他格式：直接获取
            delivery_order = callback_data.get('deliveryOrder', {})
            order_lines_data = callback_data.get('orderLines', {})
            packages_data = callback_data.get('packages', {})

        # 处理订单行数据
        if isinstance(order_lines_data, dict) and 'orderLine' in order_lines_data:
            # XML解析后的结构：orderLines.orderLine 可能是单个对象或数组
            order_line_data = order_lines_data['orderLine']
            if isinstance(order_line_data, list):
                order_lines = order_line_data
            else:
                order_lines = [order_line_data]
        else:
            order_lines = order_lines_data if isinstance(order_lines_data, list) else []

        # 处理包裹数据
        if isinstance(packages_data, dict) and 'package' in packages_data:
            package_data = packages_data['package']
            if isinstance(package_data, list):
                packages = package_data
            else:
                packages = [package_data]
        else:
            packages = packages_data if isinstance(packages_data, list) else []

        # 验证必需字段
        if not delivery_order:
            return callback_return("1", "failure", "缺少发货单信息")

        # 获取发货单基本信息
        delivery_order_code = delivery_order.get('deliveryOrderCode')
        delivery_order_id = delivery_order.get('deliveryOrderId')
        warehouse_code = delivery_order.get('warehouseCode')
        order_type = delivery_order.get('orderType')
        order_confirm_time = delivery_order.get('orderConfirmTime')
        out_biz_code = delivery_order.get('outBizCode')

        # 验证必填字段
        if not delivery_order_code:
            return callback_return("1", "failure", "缺少发货单号")

        if not delivery_order_id:
            return callback_return("1", "failure", "缺少仓储系统订单号")

        if not warehouse_code:
            return callback_return("1", "failure", "缺少仓库编码")

        if not order_type:
            return callback_return("1", "failure", "缺少出库单类型")

        if not order_confirm_time:
            return callback_return("1", "failure", "缺少订单完成时间")

        if not out_biz_code:
            return callback_return("1", "failure", "缺少外部业务编码")

        # 获取扩展属性
        extend_props = delivery_order.get('extendProps', {})
        logistics_flag = extend_props.get('logistics_flag', '')

        # 查找对应的销售订单
        try:
            # 检查是否已经处理过这个发货单，避免重复处理
            existing_delivery_notes = frappe.get_all(
                "Delivery Note",
                filters={
                    "name": delivery_order_code,
                    "docstatus": ["!=", 2]  # 不是已取消的
                },
                fields=["name", "docstatus"]
            )

            if existing_delivery_notes:
                frappe.logger().info(f"发货单 {delivery_order_code} 已存在: {[dn.name for dn in existing_delivery_notes]}")
                return callback_return(
                    "0",
                    "success",
                    f"发货单 {delivery_order_code} 已处理过，避免重复处理",
                )

            # 查找对应的销售订单
            sales_orders = frappe.get_all(
                "Sales Order",
                filters={"name": delivery_order_code},
                fields=["name", "docstatus", "status"]
            )

            if sales_orders:
                sales_order = frappe.get_doc("Sales Order", delivery_order_code)

                # 处理发货单确认
                try:
                    # 创建发货单（Delivery Note）
                    result = create_delivery_note_from_callback(
                        sales_order,
                        order_lines,
                        packages,
                        delivery_order_id,
                        warehouse_code,
                        order_type,
                        order_confirm_time,
                        out_biz_code,
                        logistics_flag,
                        callback_data
                    )

                    # 检查返回结果
                    if isinstance(result, dict) and result.get('flag') == 'failure':
                        # 如果是失败响应，直接返回
                        return result
                    elif not result:
                        return callback_return("1", "failure", "发货单创建失败")

                except Exception as e:
                    frappe.logger().error(f"创建发货单失败: {str(e)}")
                    return callback_return("1", "failure", f"创建发货单失败: {str(e)}")

                # 提交数据库事务
                frappe.db.commit()

                return callback_return("0", "success", "发货单确认处理成功")
            else:
                # 即使找不到销售订单，也返回成功，避免旺店通重复回调
                return callback_return(
                    "0",
                    "success",
                    f"回调处理成功（未找到对应销售订单: {delivery_order_code}）",
                )

        except Exception as e:
            frappe.logger().error(f"处理发货单确认失败: {str(e)}")
            frappe.db.rollback()
            return callback_return("1", "failure", f"处理发货单确认失败: {str(e)}")

    except Exception as e:
        frappe.logger().error(f"旺店通发货单回调处理异常: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return callback_return("1", "failure", f"回调处理异常: {str(e)}")

def create_delivery_note_from_callback(sales_order, order_lines, packages, delivery_order_id, warehouse_code, order_type, order_confirm_time, out_biz_code, logistics_flag, raw_callback=None):
    """
    根据旺店通回调数据自动创建发货单（Delivery Note）

    Args:
        sales_order: 销售订单文档
        order_lines: 旺店通回调的商品明细
        packages: 旺店通回调的包裹信息
        delivery_order_id: 仓储系统订单号
        warehouse_code: 仓库编码
        order_type: 出库单类型
        order_confirm_time: 订单完成时间
        out_biz_code: 外部业务编码
        logistics_flag: 物流标志

    Returns:
        Delivery Note文档对象或None
    """
    try:
        from frappe.utils import get_datetime, format_datetime, now_datetime

        # 处理包裹信息 - 计算总重量和包材数量
        total_weight = 0.0
        total_package_materials = 0
        package_materials = []  # 存储包材详细信息

        for package in packages:
            # 计算总重量
            package_weight = float(package.get('weight', 0))
            total_weight += package_weight

            # 处理包材信息
            package_material_list = package.get('packageMaterialList', {})
            if isinstance(package_material_list, dict) and 'packageMaterial' in package_material_list:
                materials = package_material_list['packageMaterial']
                if not isinstance(materials, list):
                    materials = [materials]

                for material in materials:
                    material_type = material.get('type', '')
                    material_quantity = float(material.get('quantity', 0))

                    if material_type and material_quantity > 0:
                        total_package_materials += material_quantity
                        package_materials.append({
                            'type': material_type,
                            'quantity': material_quantity
                        })

        # 更新销售订单的自定义字段（使用 db_set，避免提交后不可写限制）
        try:
            sales_order_doc = frappe.get_doc("Sales Order", sales_order.name)
            if sales_order_doc.meta.has_field("custom_delivery_weight"):
                sales_order_doc.db_set("custom_delivery_weight", float(total_weight or 0), update_modified=False)
            if sales_order_doc.meta.has_field("custom_package_num"):
                sales_order_doc.db_set("custom_package_num", float(total_package_materials or 0), update_modified=False)
            # 同步保存回调原始数据（若提供），便于排查；与后续兜底写入保持一致
            if sales_order_doc.meta.has_field("custom_wdt_callback_data") and raw_callback is not None:
                try:
                    payload_to_save = None
                    if isinstance(raw_callback, dict):
                        payload_to_save = json.dumps(raw_callback, ensure_ascii=False)
                    else:
                        payload_to_save = str(raw_callback or "")
                    sales_order_doc.db_set("custom_wdt_callback_data", payload_to_save, update_modified=False)
                except Exception:
                    # 忽略异常，不影响主流程
                    pass
            frappe.logger().info(f"已更新销售订单 {sales_order.name} 的重量: {total_weight}kg, 包材数量: {total_package_materials}")
        except Exception as e:
            frappe.logger().error(f"更新销售订单自定义字段失败: {str(e)}")

        # 更新 Logistics Order Inst 的仓库重量
        try:
            from . import api_traking
            api_traking.create_or_update_logistics_order_inst(
                sales_order_name=sales_order.name,
                warehouse_weight=total_weight,
            )
        except Exception as e:
            frappe.logger().error(f"更新 Logistics Order Inst 仓库重量失败: {str(e)}")
            # 不影响主流程，继续执行

        # 创建发货单
        delivery_note = frappe.new_doc("Delivery Note")
        delivery_note.naming_series = "DN-.YYYY.-"
        delivery_note.company = sales_order.company
        delivery_note.posting_date = get_datetime(order_confirm_time).date() if order_confirm_time else frappe.utils.today()
        delivery_note.posting_time = get_datetime(order_confirm_time).time() if order_confirm_time else frappe.utils.now_time()

        # 设置客户信息
        delivery_note.customer = sales_order.customer
        delivery_note.customer_name = sales_order.customer_name

        # 设置货币信息（确保与销售订单一致）
        delivery_note.currency = sales_order.currency
        delivery_note.conversion_rate = sales_order.conversion_rate
        delivery_note.plc_conversion_rate = sales_order.plc_conversion_rate

        # 设置地址信息
        if sales_order.shipping_address_name:
            delivery_note.shipping_address_name = sales_order.shipping_address_name
        if sales_order.customer_address:
            delivery_note.customer_address = sales_order.customer_address

        # 设置备注信息 - 包含完整的OMS回调信息
        delivery_note.remarks = f"OMS发货确认 - 销售订单: {sales_order.name}\n"
        delivery_note.remarks += f"仓储单号: {delivery_order_id}\n"
        delivery_note.remarks += f"仓库编码: {warehouse_code}\n"
        delivery_note.remarks += f"出库类型: {order_type}\n"
        delivery_note.remarks += f"外部业务编码: {out_biz_code}\n"
        delivery_note.remarks += f"确认时间: {order_confirm_time}\n"
        delivery_note.remarks += f"总重量: {total_weight}kg\n"
        delivery_note.remarks += f"包材总数: {total_package_materials}\n"
        if logistics_flag:
            delivery_note.remarks += f"物流标志: {logistics_flag}\n"

        # 根据packages中的items处理发货单商品 - 关注实际出货的包裹内容
        delivery_note.items = []
        frappe.logger().info(f"开始处理包裹商品，共 {len(packages)} 个包裹")

        for package_idx, package in enumerate(packages):
            frappe.logger().info(f"处理第 {package_idx + 1} 个包裹")

            # 处理包裹中的商品
            package_items = package.get('items', {})
            if isinstance(package_items, dict) and 'item' in package_items:
                items_data = package_items['item']
                if not isinstance(items_data, list):
                    items_data = [items_data]

                for item_idx, package_item in enumerate(items_data):
                    item_code = package_item.get('itemCode', '')
                    item_quantity = float(package_item.get('quantity', 0))

                    if not item_code or item_quantity <= 0:
                        frappe.logger().warning(f"包裹 {package_idx + 1} 第 {item_idx + 1} 个商品无效: {item_code}, 数量: {item_quantity}")
                        continue

                    frappe.logger().info(f"包裹商品: {item_code}, 数量: {item_quantity}")

                    # 查找销售订单中对应的商品信息
                    so_item = None
                    for so_item_data in sales_order.items:
                        if so_item_data.item_code == item_code:
                            so_item = so_item_data
                            break

                    if not so_item:
                        frappe.logger().error(f"销售订单中未找到商品: {item_code}")
                        continue

                    # 添加商品行
                    delivery_note_item = delivery_note.append("items")
                    delivery_note_item.item_code = item_code
                    delivery_note_item.item_name = so_item.item_name
                    delivery_note_item.description = so_item.description
                    delivery_note_item.qty = item_quantity
                    delivery_note_item.uom = so_item.uom
                    delivery_note_item.stock_uom = so_item.stock_uom
                    delivery_note_item.conversion_factor = so_item.conversion_factor
                    delivery_note_item.rate = so_item.rate
                    delivery_note_item.amount = so_item.rate * item_quantity
                    delivery_note_item.warehouse = so_item.warehouse
                    delivery_note_item.against_sales_order = sales_order.name
                    delivery_note_item.so_detail = so_item.name
                    delivery_note_item.remarks = f"OMS确认发货, 包裹内数量: {item_quantity}"

                    frappe.logger().info(f"已添加商品: {item_code}, 最终发货数量: {item_quantity}")

        # 检查是否有商品明细
        if not delivery_note.items:
            frappe.logger().error("没有有效的商品明细，无法创建发货单")
            return None

        frappe.logger().info(f"发货单商品明细总数: {len(delivery_note.items)}")
        for idx, dn_item in enumerate(delivery_note.items):
            frappe.logger().info(f"发货单第 {idx + 1} 行: {dn_item.item_code}, 数量={dn_item.qty}, 关联订单={dn_item.against_sales_order or '无'}")

        # 处理包材信息 - 作为独立项目添加到发货单（不关联销售订单）
        if package_materials:
            try:
                for material in package_materials:
                    material_type = material['type']
                    material_quantity = material['quantity']

                    # 根据包材类型查找对应的SKU - 直接使用type作为item_code
                    item_code = None
                    try:
                        # 直接使用包材类型作为item_code
                        if frappe.db.exists("Item", material_type):
                            item_code = material_type
                        else:
                            frappe.logger().error(f"未找到包材类型 '{material_type}' 对应的SKU")
                            return callback_return("1", "failure", f"包材类型 '{material_type}' 对应的SKU不存在")

                        # 添加包材到发货单（作为独立项目，不关联销售订单）
                        if item_code:
                            # 获取包材商品的默认单位和价格
                            item_doc = frappe.get_doc("Item", item_code)
                            default_uom = item_doc.stock_uom or "Nos"
                            default_rate = item_doc.valuation_rate or 0  # 使用估值价格

                            package_item = delivery_note.append("items")
                            package_item.item_code = item_code
                            package_item.item_name = item_doc.item_name
                            package_item.description = f"包材 - {material_type}"
                            package_item.qty = material_quantity
                            package_item.uom = default_uom
                            package_item.stock_uom = default_uom
                            package_item.conversion_factor = 1.0
                            package_item.rate = default_rate
                            package_item.amount = default_rate * material_quantity
                            package_item.warehouse = delivery_note.items[0].warehouse if delivery_note.items else None
                            package_item.remarks = f"包材 - {material_type} (耗材，不关联销售订单)"

                            # 注意：包材不设置 against_sales_order 和 so_detail，因为它们是耗材

                            frappe.logger().info(f"已添加包材: {material_type} (SKU: {item_code}), 数量: {material_quantity}")

                    except Exception as e:
                        frappe.logger().error(f"处理包材 {material_type} 失败: {str(e)}")
                        # 包材处理失败不影响整体流程，继续处理
                        continue

            except Exception as e:
                frappe.logger().error(f"处理包材信息失败: {str(e)}")
                # 包材处理失败不影响整体流程，继续处理

        # 保存发货单
        try:
            delivery_note.insert(ignore_permissions=True)
            frappe.logger().info(f"发货单已保存: {delivery_note.name}")
        except Exception as e:
            frappe.logger().error(f"保存发货单失败: {str(e)}")
            return callback_return("0", "success", "发货单保存失败，但已记录日志")

        # 提交发货单并设置状态（合并操作减少系统开销）
        try:
            # 设置允许负库存（电商场景下，实际已发货但库存可能不足）
            delivery_note.allow_negative_stock = 1

            # 电商场景：发货单提交后直接设置为完成状态，不需要等待开票
            delivery_note.status = "Completed"

            # 一次性提交、保存和提交数据库事务
            delivery_note.submit()
            frappe.db.commit()

            frappe.logger().info(f"发货单已提交并设置为完成状态: {delivery_note.name}")

        except Exception as e:
            frappe.logger().error(f"提交发货单失败: {str(e)}")
            # 即使提交失败，也给旺店通返回成功，避免重复回调
            return callback_return("0", "success", "发货单提交失败，但已记录日志")

        # 更新销售订单状态：发货确认后进入“待开票”(To Bill) 状态，并顺便回写重量/原始回调信息
        try:
            sales_order_doc = frappe.get_doc("Sales Order", sales_order.name)
            if sales_order_doc.status not in ["To Bill", "Completed", "Cancelled", "Closed"]:
                try:
                    sales_order_doc.db_set("status", "To Bill", update_modified=False)
                except Exception:
                    sales_order_doc.status = "To Bill"
            # 直接回写重量/包材数量（按字段存在与否设置），以及原始回调数据
            if sales_order_doc.meta.has_field("custom_delivery_weight"):
                try:
                    sales_order_doc.db_set("custom_delivery_weight", float(total_weight or 0), update_modified=False)
                except Exception:
                    sales_order_doc.custom_delivery_weight = total_weight
            if sales_order_doc.meta.has_field("custom_package_num"):
                try:
                    sales_order_doc.db_set("custom_package_num", float(total_package_materials or 0), update_modified=False)
                except Exception:
                    sales_order_doc.custom_package_num = total_package_materials
            if sales_order_doc.meta.has_field("custom_wdt_callback_data"):
                try:
                    payload_to_save = None
                    if isinstance(callback_data, dict):
                        payload_to_save = json.dumps(callback_data, ensure_ascii=False)
                    else:
                        payload_to_save = str(callback_data or "") or (raw_data or "")
                    try:
                        sales_order_doc.db_set("custom_wdt_callback_data", payload_to_save, update_modified=False)
                    except Exception:
                        sales_order_doc.custom_wdt_callback_data = payload_to_save
                except Exception:
                    # 兜底保存原始请求体
                    try:
                        sales_order_doc.db_set("custom_wdt_callback_data", raw_data or "", update_modified=False)
                    except Exception:
                        sales_order_doc.custom_wdt_callback_data = raw_data or ""
            # 若前面的 db_set 失败才回退到 save
            try:
                sales_order_doc.save(ignore_permissions=True)
            except Exception:
                pass
            frappe.db.commit()
            frappe.logger().info(f"销售订单已回写并更新为待开票(To Bill): {sales_order.name}")
        except Exception as e:
            frappe.logger().error(f"回写销售订单失败: {str(e)}")
            # 回写失败不影响整体流程


        return delivery_note

    except Exception as e:
        frappe.logger().error(f"创建发货单失败: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        # 即使创建失败，也给旺店通返回成功，避免重复回调
        return callback_return("0", "success", "发货单创建失败，但已记录日志")

def create_material_receipt_from_callback(material_request, order_lines, entry_order_id, warehouse_code, operate_time, entry_order_type, out_biz_code, confirm_type, remark, status):
    """
    根据旺店通回调数据自动创建物料入库单（Stock Entry）

    Args:
        material_request: 物料申请单文档
        order_lines: 旺店通回调的商品明细
        entry_order_id: 仓储系统入库单ID
        warehouse_code: 仓库编码
        operate_time: 操作时间
        entry_order_type: 入库类型
        out_biz_code: 外部业务编码
        confirm_type: 确认类型
        remark: 备注
        status: 入库单状态（FULFILLED/PARTFULFILLED）

    Returns:
        Stock Entry文档对象或None
    """
    try:
        from frappe.utils import get_datetime, format_datetime, now_datetime

        # 创建物料入库单
        stock_entry = frappe.new_doc("Stock Entry")
        stock_entry.stock_entry_type = "Material Receipt"  # 物料入库
        stock_entry.company = material_request.company
        stock_entry.posting_date = get_datetime(operate_time).date() if operate_time else frappe.utils.today()
        stock_entry.posting_time = get_datetime(operate_time).time() if operate_time else frappe.utils.now_time()

        # 设置备注信息 - 包含完整的OMS回调信息
        stock_entry.remarks = f"OMS入库确认 - 物料申请单: {material_request.name}\n"
        stock_entry.remarks += f"仓储单号: {entry_order_id}\n"
        stock_entry.remarks += f"仓库编码: {warehouse_code}\n"
        stock_entry.remarks += f"入库类型: {entry_order_type}\n"
        stock_entry.remarks += f"外部业务编码: {out_biz_code}\n"
        stock_entry.remarks += f"确认类型: {confirm_type}\n"
        stock_entry.remarks += f"确认时间: {operate_time}\n"
        if remark:
            stock_entry.remarks += f"备注: {remark}\n"

        # 添加物料申请单关联 - 使用自定义字段
        if hasattr(stock_entry, 'material_request'):
            stock_entry.material_request = material_request.name

        # 用于后续计费的总入库数量（只统计真正落地到入库单的明细），以件数为整数计量
        total_qty_for_billing = 0

        # 验证orderLine必填字段
        for i, line in enumerate(order_lines):
            item_code = line.get('itemCode', '')
            actual_qty = line.get('actualQty', '')
            inventory_type = line.get('inventoryType', '')

            # 验证必填字段
            if not item_code:
                return callback_return("1", "failure", f"第{i+1}行缺少商品编码")

            if not actual_qty or actual_qty == '':
                return callback_return("1", "failure", f"第{i+1}行缺少实收数量")

            # 验证数量格式
            try:
                actual_qty = float(actual_qty)
                if actual_qty <= 0:
                    return callback_return("1", "failure", f"第{i+1}行实收数量必须大于0")
            except (ValueError, TypeError):
                return callback_return("1", "failure", f"第{i+1}行实收数量格式错误")

        # 添加商品明细
        for line in order_lines:
            item_code = line.get('itemCode', '')
            # 按照旺店通接口文档，使用 actualQty 字段
            actual_qty = float(line.get('actualQty', 0))
            order_line_no = line.get('orderLineNo', '')
            inventory_type = line.get('inventoryType', 'ZP')

            if not item_code or actual_qty <= 0:
                continue

            # 获取商品信息
            try:
                item_doc = frappe.get_doc("Item", item_code)
            except frappe.DoesNotExistError:
                continue

            # 检查物料申请单明细中是否存在该商品
            material_request_item = None
            for mr_item in material_request.items:
                if mr_item.item_code == item_code:
                    material_request_item = mr_item
                    break

            if not material_request_item:
                continue

            # 添加商品行
            stock_entry_item = stock_entry.append("items")
            stock_entry_item.item_code = item_code
            stock_entry_item.item_name = item_doc.item_name
            stock_entry_item.description = item_doc.description
            stock_entry_item.qty = actual_qty
            # 计费使用的数量累加（只统计真正插入入库单的明细）；按“件数”以整数计量
            try:
                total_qty_for_billing += int(actual_qty or 0)
            except Exception:
                # 如果 actual_qty 非整数格式，退回使用四舍五入后的整数
                total_qty_for_billing += int(round(float(actual_qty or 0)))
            stock_entry_item.uom = item_doc.stock_uom
            stock_entry_item.stock_uom = item_doc.stock_uom
            stock_entry_item.conversion_factor = 1.0
            stock_entry_item.basic_rate = 0.0  # 物料申请单通常没有价格
            stock_entry_item.amount = 0.0

            # 设置物料申请单关联字段
            stock_entry_item.material_request = material_request.name
            stock_entry_item.material_request_item = material_request_item.name

            # 设置目标仓库
            if material_request.set_warehouse:
                stock_entry_item.t_warehouse = material_request.set_warehouse
            else:
                # 如果没有设置目标仓库，使用默认仓库
                stock_entry_item.t_warehouse = "Stores - PAIRE"  # 根据截图中的默认值

            # 添加商品行备注 - 包含OMS详细信息
            item_remarks = f"OMS行号: {order_line_no}, 库存类型: {inventory_type}, 实收数量: {actual_qty}"

            # 暂时忽略批次号管理 - 只记录到备注中
            batchs_data = line.get('batchs', {})
            if isinstance(batchs_data, dict) and 'batch' in batchs_data:
                # XML解析后的结构：batchs.batch 是数组
                batchs = batchs_data['batch']
                if not isinstance(batchs, list):
                    batchs = [batchs]  # 如果只有一个元素，转换为数组
            else:
                # JSON格式或其他格式
                batchs = batchs_data if isinstance(batchs_data, list) else []

            if batchs and len(batchs) > 0:
                # 只记录批次信息到备注，不设置到字段中
                batch = batchs[0]
                batch_code = batch.get('batchCode', '')
                product_date = batch.get('productDate', '')
                expire_date = batch.get('expireDate', '')
                batch_qty = batch.get('actualQty', 0)
                batch_inventory_type = batch.get('inventoryType', '')

                # 添加批次信息到备注（不设置到字段中）
                item_remarks += f"\n批次信息: {batch_code}, 生产日期: {product_date}, 过期日期: {expire_date}, 数量: {batch_qty}, 类型: {batch_inventory_type}"

            # 添加序列号信息（如果有）
            sn_list = line.get('snList', [])
            if sn_list and len(sn_list) > 0:
                # 将序列号信息添加到备注中
                sn_codes = [sn.get('sn', '') for sn in sn_list if sn.get('sn')]
                if sn_codes:
                    item_remarks += f"\n序列号: {', '.join(sn_codes)}"

            # 添加扩展属性信息
            extend_props = line.get('extendProps', {})
            if extend_props:
                aux_unit_ratio = extend_props.get('auxUnitRatio', '')
                unit_ratio = extend_props.get('unitRatio', '')
                vp_package_no = extend_props.get('VpPackageNO', '')
                vp_box_no = extend_props.get('VpBoxNO', '')
                prop1 = extend_props.get('prop1', '')
                prop2 = extend_props.get('prop2', '')
                prop3 = extend_props.get('prop3', '')
                material_request_line = extend_props.get('material_request_line', '')

                if material_request_line:
                    item_remarks += f"\n物料申请单行号: {material_request_line}"

                if any([aux_unit_ratio, unit_ratio, vp_package_no, vp_box_no, prop1, prop2, prop3]):
                    item_remarks += f"\n扩展属性:"
                    if aux_unit_ratio:
                        item_remarks += f" 每包数量: {aux_unit_ratio}"
                    if unit_ratio:
                        item_remarks += f" 每箱数量: {unit_ratio}"
                    if vp_package_no:
                        item_remarks += f" 唯品会PO单号: {vp_package_no}"
                    if vp_box_no:
                        item_remarks += f" 唯品会入库箱号: {vp_box_no}"
                    if prop1:
                        item_remarks += f" 自定义属性1: {prop1}"
                    if prop2:
                        item_remarks += f" 自定义属性2: {prop2}"
                    if prop3:
                        item_remarks += f" 自定义属性3: {prop3}"

            # 设置商品行备注
            stock_entry_item.remarks = item_remarks

        # 检查是否有商品明细
        if not stock_entry.items:
            return None

        # 保存物料入库单
        stock_entry.insert(ignore_permissions=True)

        # 在提交前添加状态更新逻辑
        def update_material_request_status(status):
            """更新物料申请单状态"""
            try:
                # 重新获取物料申请单，确保数据最新
                material_request_doc = frappe.get_doc("Material Request", material_request.name)

                # 根据入库状态更新物料申请单状态
                if status == 'FULFILLED':
                    # 完全收货完成
                    material_request_doc.status = 'Completed'
                elif status == 'PARTFULFILLED':
                    # 部分收货完成
                    material_request_doc.status = 'Partially Ordered'

                # 保存物料申请单状态更新
                material_request_doc.save(ignore_permissions=True)
                frappe.db.commit()

            except Exception as e:
                frappe.logger().error(f"更新物料申请单状态失败: {str(e)}")

        # 提交入库单
        stock_entry.submit()

        # 提交成功后更新物料申请单状态
        update_material_request_status(status)

        # 提交成功后，按业务触发入库计费
        # 说明：
        # - 这里只负责把 company / 业务单号(source_id) / 总件数 / 业务交易时间 交给计费模块
        # - 具体如何定位账期、解析价格、写入 Account Billing Detail Inst 由 api_billing 统一处理
        # - 若后续再跑账期流水，_handle_inbound_billing 会根据 source_type/source_id 去重，避免重复计费
        try:
            from . import api_billing as billing_mod

            company = getattr(material_request, "company", None)
            # 业务交易时间：优先使用 operate_time，其次使用入库单 posting_date
            event_time = operate_time or stock_entry.posting_date
            # 业务单号：这里使用物料申请单号作为计费 source_id，避免依赖内部 Stock Entry 名称
            source_id = material_request.name
            if company and total_qty_for_billing and source_id:
                billing_mod.create_inbound_billing_for_receipt(
                    company=company,
                    total_qty=int(total_qty_for_billing),
                    source_id=source_id,
                    event_time=str(event_time),
                )
        except Exception:
            # 计费失败不影响入库回调主流程，只记录警告日志
            frappe.logger().error(
                f"[Billing][Inbound] auto billing for Stock Entry {getattr(stock_entry, 'name', '?')} failed: {frappe.get_traceback()}"
            )

        return stock_entry

    except Exception as e:
        frappe.logger().error(f"创建物料入库单失败: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return None

@frappe.whitelist()
def test_api_connection():
    """
    测试 API 连接
    """
    return {
        "success": True,
        "message": _("API 连接正常"),
        "timestamp": frappe.utils.now(),
        "user": frappe.session.user
    }

# ========================================
# 云途API接口封装
# ========================================

@frappe.whitelist(allow_guest=True)
def get_tracking_info(tracking_number):
    """
    获取跟踪信息 - 使用云途轨迹查询接口
    """
    try:

        # 使用轨迹查询接口获取完整轨迹信息
        result = yt.get_order_track(tracking_number)

        if result.get('success'):
            # 解析云途返回的数据，转换为前端需要的格式
            tracking_data = parse_tracking_info(result)
            return {
                "success": True,
                "data": tracking_data,
                "message": "获取跟踪信息成功"
            }
        else:
            frappe.logger().error(f"API查询失败: {result.get('msg', '未知错误')}")
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取跟踪信息失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取跟踪信息失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取跟踪信息失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_tracking_history(tracking_number):
    """
    获取跟踪历史
    """
    try:
        result = yt.get_order_track(tracking_number)

        if result.get('success'):
            # 解析云途返回的历史数据
            history_data = parse_tracking_history(result)
            return {
                "success": True,
                "data": history_data,
                "message": "获取跟踪历史成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取跟踪历史失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取跟踪历史失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取跟踪历史失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_carrier_info(tracking_number):
    """
    获取承运商信息
    """
    try:
        result = yt.get_order_track(tracking_number)

        if result.get('success'):
            # 解析云途返回的承运商信息
            carrier_data = parse_carrier_info(result)
            return {
                "success": True,
                "data": carrier_data,
                "message": "获取承运商信息成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取承运商信息失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取承运商信息失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取承运商信息失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def batch_tracking(tracking_numbers):
    """
    批量跟踪查询
    """
    try:
        # 确保tracking_numbers是列表格式
        if isinstance(tracking_numbers, str):
            # 如果是字符串，尝试解析为JSON
            import json
            try:
                tracking_numbers = json.loads(tracking_numbers)
            except:
                # 如果不是JSON，按逗号分割
                tracking_numbers = [num.strip() for num in tracking_numbers.split(',') if num.strip()]

        # 确保是列表
        if not isinstance(tracking_numbers, list):
            tracking_numbers = [tracking_numbers]

        # 过滤空值
        tracking_numbers = [num for num in tracking_numbers if num and str(num).strip()]

        frappe.logger().info(f"批量跟踪查询 - 跟踪号列表: {tracking_numbers}")
        try:
            frappe.logger().error(f"[YT Tracking] 批量查询请求列表: {json.dumps(tracking_numbers, ensure_ascii=False)}")
        except Exception:
            frappe.logger().error(f"[YT Tracking] 批量查询请求列表: {tracking_numbers}")

        results = []

        for tracking_number in tracking_numbers:
            # 确保跟踪号是字符串且去除空格
            tracking_number = str(tracking_number).strip()
            if not tracking_number:
                continue

            # 对外显示统一为FG，查询前若是FG前缀，转换为YT再查
            display_tracking_number = tracking_number
            query_tracking_number = tracking_number
            if len(tracking_number) >= 2 and tracking_number[:2].upper() == "FG":
                query_tracking_number = "YT" + tracking_number[2:]

            frappe.logger().error(f"[YT Tracking] 请求参数 order_number={query_tracking_number}")
            result = yt.get_order_track(query_tracking_number)
            try:
                response_dump = json.dumps(result, ensure_ascii=False)
            except Exception:
                response_dump = str(result)
            frappe.logger().error(f"[YT Tracking] 响应 order_number={query_tracking_number}: {response_dump}")

            if result.get('success'):
                tracking_data = parse_tracking_info(result)
                # 将返回中的任何以YT开头的号替换为FG进行展示
                try:
                    if isinstance(tracking_data, dict):
                        for k in ("tracking_number", "last_mile_tracking", "waybill_number"):
                            v = tracking_data.get(k)
                            if isinstance(v, str) and len(v) >= 2 and v[:2].upper() == "YT":
                                tracking_data[k] = "FG" + v[2:]
                except Exception:
                    pass
                results.append({
                    "tracking_number": display_tracking_number,
                    "query_tracking_number": query_tracking_number,
                    "success": True,
                    "data": tracking_data
                })
            else:
                results.append({
                    "tracking_number": display_tracking_number,
                    "query_tracking_number": query_tracking_number,
                    "success": False,
                    "message": result.get('msg', '查询失败')
                })

        try:
            frappe.logger().error(f"[YT Tracking] 批量查询响应结果: {json.dumps(results, ensure_ascii=False)}")
        except Exception:
            frappe.logger().error(f"[YT Tracking] 批量查询响应结果: {results}")

        return {
            "success": True,
            "data": results,
            "message": "批量跟踪查询完成"
        }
    except Exception as e:
        frappe.logger().error(f"批量跟踪查询失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"批量跟踪查询失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_country_codes():
    """
    获取国家代码列表
    """
    try:
        result = yt.get_country_code()

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}).get('list', []),
                "message": "获取国家代码成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取国家代码失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取国家代码失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取国家代码失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_logistics_products(country_code=None):
    """
    获取物流产品列表
    """
    try:
        result = yt.get_logistics_product(country_code)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('detail', []),
                "message": "获取物流产品成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取物流产品失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取物流产品失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取物流产品失败: {str(e)}"
        }


@frappe.whitelist(allow_guest=True)
def create_order(order_data):
    """
    创建订单
    """
    try:
        result = yt.single_ticket_send(order_data)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "创建订单成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '创建订单失败')
            }
    except Exception as e:
        frappe.logger().error(f"创建订单失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"创建订单失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def update_order_weight(weight_data):
    """
    修改运单预报重量
    """
    try:
        result = yt.update_order_weight(weight_data)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "修改重量成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '修改重量失败')
            }
    except Exception as e:
        frappe.logger().error(f"修改重量失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"修改重量失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def cancel_order(cancel_data):
    """
    撤销运单
    """
    try:
        result = yt.cancel_order(cancel_data)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "撤销运单成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '撤销运单失败')
            }
    except Exception as e:
        frappe.logger().error(f"撤销运单失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"撤销运单失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def intercept_order(intercept_data):
    """
    拦截运单
    """
    try:
        # 兼容前端把对象序列化为字符串的情况
        try:
            if isinstance(intercept_data, str):
                import json as _json
                parsed = _json.loads(intercept_data) if intercept_data.strip() else {}
            elif isinstance(intercept_data, dict):
                parsed = intercept_data
            else:
                parsed = {}
        except Exception:
            parsed = {}

        result = yt.intercept_order(parsed)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "拦截运单成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '拦截运单失败')
            }
    except Exception as e:
        frappe.logger().error(f"拦截运单失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"拦截运单失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_order_label(query_data):
    """
    获取订单标签
    """
    try:
        result = yt.get_order_label(query_data)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "获取订单标签成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取订单标签失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取订单标签失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取订单标签失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def get_order_detail(query_data):
    """
    获取订单详情
    """
    try:
        # 兼容前端将 dict 序列化为字符串的情况
        try:
            if isinstance(query_data, str):
                import json as _json
                parsed = _json.loads(query_data) if query_data.strip() else {}
            elif isinstance(query_data, dict):
                parsed = query_data
            else:
                parsed = {}
        except Exception:
            parsed = {}

        # 允许用运单号/客户单号/跟踪号统一放在 order_number；若前端只传了 waybill_number 也兼容
        order_number = (parsed.get('order_number')
                        or parsed.get('waybill_number')
                        or parsed.get('tracking_number'))
        params = {'order_number': order_number} if order_number else {}

        result = yt.get_order_detail(params)

        if result.get('success'):
            return {
                "success": True,
                "data": result.get('result', {}),
                "message": "获取订单详情成功"
            }
        else:
            return {
                "success": False,
                "data": None,
                "message": result.get('msg', '获取订单详情失败')
            }
    except Exception as e:
        frappe.logger().error(f"获取订单详情失败: {str(e)}")
        return {
            "success": False,
            "data": None,
            "message": f"获取订单详情失败: {str(e)}"
        }

@frappe.whitelist(allow_guest=True)
def test_yt_connection():
    """
    测试云途API连接
    """
    try:
        result = yt.test_connection()
        return result
    except Exception as e:
        frappe.logger().error(f"测试云途API连接失败: {str(e)}")
        return {
            "success": False,
            "message": f"测试云途API连接失败: {str(e)}"
        }

# ========================================
# 数据解析函数
# ========================================

def parse_tracking_info(result):
    """
    解析云途返回的跟踪信息
    """
    try:
        # 云途API返回的result是数组，取第一个元素
        result_list = result.get('result', [])

        if not result_list or len(result_list) == 0:
            return {}

        # 取第一个跟踪信息
        data = result_list[0]

        if not isinstance(data, dict):
            return {}

        # 从track_Info中提取详细信息
        track_info = data.get('track_Info', {})

        # 获取包裹状态，优先使用track_Info中的状态
        package_status = track_info.get('package_status', data.get('package_status', 'unknown'))

        # 获取轨迹事件
        track_events = track_info.get('track_events', [])

        # 提取基本信息
        tracking_info = {
            "tracking_number": data.get('order_number', ''),
            "last_mile_tracking": track_info.get('tracking_number', ''),
            "order_number": track_info.get('customer_order_number', '-'),
            "waybill_number": track_info.get('shipment_number', ''),
            "product_name": track_info.get('product_name', ''),
            "package_status": package_status,
            "status": map_package_status(package_status),
            "route": f"{track_info.get('origin_code', '')} → {track_info.get('destination_code', '')}",
            "weight": track_info.get('actual_weight', 0),
            "interval_day": track_info.get('interval_day', 0),
            "last_event": get_last_event(track_events),
            "tracking_history": format_tracking_history(track_events),
            "carrier_info": {
                "name": track_info.get('last_mile_name', '云途物流'),
                "website": track_info.get('last_mile_site', 'https://www.yunexpress.com'),
                "phone": track_info.get('phone_number', '+86 400-123-4567')
            }
        }

        return tracking_info
    except Exception as e:
        frappe.logger().error(f"解析跟踪信息失败: {str(e)}")
        return {}

def parse_tracking_history(result):
    """
    解析云途返回的跟踪历史
    """
    try:
        # 云途API返回的result是数组，取第一个元素
        result_list = result.get('result', [])
        frappe.logger().info(f"parse_tracking_history提取的result字段: {result_list}")

        if not result_list or len(result_list) == 0:
            frappe.logger().warning("result字段为空数组")
            return []

        # 取第一个跟踪信息
        data = result_list[0]
        track_info = data.get('track_Info', {})
        history_list = track_info.get('track_events', [])

        frappe.logger().info(f"track_events字段: {history_list}")

        history = []
        for item in history_list:
            history.append({
                "timestamp": item.get('process_time', ''),
                "utc_time": item.get('process_utc_time', ''),
                "status": map_track_node_code(item.get('track_node_code', 'unknown')),
                "description": item.get('process_content', ''),
                "location": f"{item.get('process_country', '')} {item.get('process_province', '')} {item.get('process_city', '')}".strip(),
                "track_node_code": item.get('track_node_code', ''),
                "pod_url": item.get('pod_url', '')
            })

        frappe.logger().info(f"parse_tracking_history解析后的历史记录: {history}")
        return history
    except Exception as e:
        frappe.logger().error(f"解析跟踪历史失败: {str(e)}")
        frappe.logger().error(f"解析跟踪历史失败时的原始数据: {result}")
        return []

def parse_carrier_info(result):
    """
    解析云途返回的承运商信息
    """
    try:
        data = result.get('result', {})

        carrier_info = {
            "name": "云途物流",
            "website": "https://www.yunexpress.com",
            "email": "service@yunexpress.com",
            "phone": "+86-755-2518-0000",
            "working_hours": "Monday to Friday, 9:00-18:00 (GMT+8)"
        }

        return carrier_info
    except Exception as e:
        frappe.logger().error(f"解析承运商信息失败: {str(e)}")
        return {}

def map_status(status):
    """
    映射云途状态到系统状态
    """
    status_map = {
        'delivered': 'delivered',
        'in_transit': 'in_transit',
        'out_for_delivery': 'out_for_delivery',
        'processing': 'processing',
        'pending': 'pending',
        'exception': 'exception'
    }

    return status_map.get(status, 'unknown')

def map_package_status(package_status):
    """
    映射云途包裹状态到系统状态
    """
    status_map = {
        'F': 'delivered',      # 已送达
        'T': 'in_transit',     # 运输中
        'P': 'processing',     # 处理中
        'E': 'exception',      # 异常
        'C': 'cancelled',      # 已取消
        'R': 'returned',       # 已退回
        'D': 'delivered'       # 已送达 (D状态)
    }

    return status_map.get(package_status, 'unknown')

def format_tracking_history(track_events):
    """
    格式化轨迹历史数据
    """
    if not track_events or not isinstance(track_events, list):
        frappe.logger().warning("轨迹事件为空或不是列表")
        return []

    formatted_events = []
    for event in track_events:
        formatted_event = {
            "timestamp": event.get('process_time', ''),
            "utc_time": event.get('process_utc_time', ''),
            "description": event.get('process_content', ''),
            "location": event.get('process_location', ''),
            "status": map_track_node_code(event.get('track_node_code', '')),
            "event": event.get('process_content', ''),
            "track_node_code": event.get('track_node_code', ''),
            "country": event.get('process_country', ''),
            "province": event.get('process_province', ''),
            "city": event.get('process_city', ''),
            "pod_url": event.get('pod_url', '')
        }
        formatted_events.append(formatted_event)

    return formatted_events

def map_track_node_code(track_node_code):
    """
    映射云途跟踪节点代码到系统状态
    """
    status_map = {
        'ORDER_CREATION': '订单创建',
        'PICKED_UP': '已取件',
        'FIRST_MILE_ARRIVE': '到达始发地设施',
        'FIRST_MILE_DEPART': '离开始发地设施',
        'ARRIVE_CONFIRM_OC': '到达始发机场',
        'MAIN_LINE_DEPART': '国际航班起飞',
        'CUSTOMS_PROCESSING': '清关处理中',
        'MAIN_LINE_ARRIVE': '国际航班到达',
        'CUSTOMS_COMPLETE': '清关完成',
        'IN_TRANSIT': '运输中',
        'TRANSITHUB_ARRIVE': '到达中转站',
        'IN_TRANSIT_CARRIER': '承运商运输中',
        'DELIVERY_ATTEMPT': '派送中',
        'DELIVERED': '已送达',
        'EXCEPTION': '异常',
        'RETURNED': '已退回'
    }

    return status_map.get(track_node_code, track_node_code)

def get_last_event(track_events):
    """
    获取最后一个跟踪事件,,,,
    """
    if not track_events or len(track_events) == 0:
        return ''

    # 按时间排序，取最后一个
    sorted_events = sorted(track_events, key=lambda x: x.get('process_time', ''))
    last_event = sorted_events[-1]

    return last_event.get('process_content', '')

# ========================================
# 云途定价查询API接口
# ========================================

@frappe.whitelist(allow_guest=True)
def get_pricing_quote(query_params):
    """
    根据目的地国家等参数返回线路详情和定价数据

    Args:
        query_params (dict): 查询参数，包含以下字段：
            - destination_country (str): 目的地国家 (必需)
            - shipping_mode (str, optional): 快运模式（经济、快速、标准）
            - cargo_type (str, optional): 走货属性（带电、服装、化妆品等）
            - length (float, optional): 长度 (cm)
            - width (float, optional): 宽度 (cm)
            - height (float, optional): 高度 (cm)
            - weight (float, optional): 发货重量 (kg)
            - destination_state (str, optional): 目的地州
            - destination_postal_code (str, optional): 目的地邮编

    Returns:
        dict: 包含线路详情、定价数据、税率信息和包装限制的响应
            - data.pricing_data: 三层嵌套结构 {国家: {线路: [运费记录]}}
            - data.products: 产品信息map {产品名称: 产品详情}
            - data.available_products: 支持该国家的所有产品
    """
    try:
        # 解析查询参数
        if isinstance(query_params, str):
            import json
            query_params = json.loads(query_params)

        # 验证必需参数 - 只有国家是必需的
        if not query_params.get('destination_country'):
            return {
                "success": False,
                "message": "缺少必需参数: destination_country",
                "data": None
            }

        # 提取参数
        destination_country = query_params.get('destination_country')
        shipping_mode = query_params.get('shipping_mode', '')
        cargo_type = query_params.get('cargo_type', '')
        length = float(query_params.get('length', 0)) if query_params.get('length') else 0
        width = float(query_params.get('width', 0)) if query_params.get('width') else 0
        height = float(query_params.get('height', 0)) if query_params.get('height') else 0
        weight = float(query_params.get('weight', 0)) if query_params.get('weight') else 0
        destination_state = query_params.get('destination_state', '')
        destination_postal_code = query_params.get('destination_postal_code', '')

        # 验证尺寸和重量参数（如果提供的话）
        if (length > 0 or width > 0 or height > 0 or weight > 0):
            if length <= 0 or width <= 0 or height <= 0 or weight <= 0:
                return {
                    "success": False,
                    "message": "如果提供尺寸和重量参数，所有参数都必须大于0",
                    "data": None
                }

        # 使用新的get_pricing_by_destination方法
        result = yt.get_pricing_by_destination(
            destination_country=destination_country,
            shipping_mode=shipping_mode,
            cargo_type=cargo_type,
            length=length,
            width=width,
            height=height,
            weight=weight,
            destination_state=destination_state,
            destination_postal_code=destination_postal_code
        )

        return result

    except Exception as e:
        frappe.logger().error(f"获取定价报价失败: {str(e)}")
        return {
            "success": False,
            "message": f"获取定价报价失败: {str(e)}",
            "data": None
        }

# ========================================
# 云途物流接口集成
# ========================================

def cancel_yunexpress_order(waybill_number):
    """
    撤销云途订单

    Args:
        waybill_number: 云途运单号

    Returns:
        dict: 撤销结果
    """
    frappe.logger().info(f"Cancel carrier shipment order: {waybill_number}")
    try:
        if not waybill_number:
            return {
                "success": False,
                "message": "运单号不能为空",
                "data": None
            }

        # 调用承运商撤销接口
        cancel_data = {"waybill_number": waybill_number}
        result = yt.cancel_order(cancel_data)

        if result.get("success"):
            frappe.logger().info(f"Carrier shipment order canceled successfully: {waybill_number}")
            return {
                "success": True,
                "message": "Shipment order canceled successfully",
                "data": result.get("result")
            }
        else:
            frappe.logger().error(f"Carrier shipment order cancel failed: {result.get('msg', 'Unknown error')}")
            return {
                "success": False,
                "message": f"Shipment order cancel failed: {result.get('msg', 'Unknown error')}",
                "data": None
            }

    except Exception as e:
        frappe.logger().error(f"Exception when canceling shipment order: {str(e)}")
        return {
            "success": False,
            "message": f"Exception when canceling shipment order: {str(e)}",
            "data": None
        }


def create_yunexpress_order(
    sales_order,
    receiver_info,
    sender_address,
    product_code=None,
):
    """
    创建云途订单（运单创建、面单获取）

    Args:
        sales_order: 销售订单文档
        receiver_info: 收件人信息字典
        sender_address: 发货地址文档（仓库地址）
        product_code: 物流产品代码（必需）

    Returns:
        dict: 包含运单号、跟踪号、面单URL等信息的字典
    """
    frappe.logger().info("创建云途订单")
    try:
        if not product_code:
            return {
                "success": False,
                "message": "缺少物流产品代码",
                "data": None
            }

        # 1. 创建云途运单
        order_data = build_yunexpress_order_data(sales_order, receiver_info, sender_address, product_code)
        create_result = yt.single_ticket_send(order_data)

        if not create_result.get("success"):
            return {
                "success": False,
                "message": f"云途运单创建失败: {create_result.get('msg', '未知错误')}",
                "data": None
            }

        result_data = create_result.get("result", {})
        waybill_number = result_data.get("waybill_number", "")
        tracking_number = result_data.get("tracking_number", "")
        # 规范化：若云途返回以 YT 开头的跟踪号，替换为本系统前缀 FG
        try:
            if isinstance(tracking_number, str) and tracking_number[:2].upper() == "YT":
                tracking_number = "FG" + tracking_number[2:]
        except Exception:
            pass
        customer_order_number = result_data.get("customer_order_number", "")

        if not waybill_number:
            return {
                "success": False,
                "message": "云途运单创建成功但未返回运单号",
                "data": None
            }

        # 2. 获取面单
        label_result = get_yunexpress_label(waybill_number)
        label_url = ""
        if label_result.get("success"):
            label_data = label_result.get("data", {})
            label_url = label_data.get("url", "")

        # 3. 保存云途订单信息到销售订单
        save_yunexpress_info_to_sales_order(sales_order, {
            "waybill_number": waybill_number,
            "tracking_number": tracking_number,
            "customer_order_number": customer_order_number,
            "label_url": label_url,
            "product_code": product_code
        })

        # 4. 创建或更新 Logistics Order Inst
        try:
            from . import api_traking
            # 获取 Shopify 订单 JSON（如果存在）
            shopify_json = None
            if hasattr(sales_order, "custom_shopify_order_json") and sales_order.custom_shopify_order_json:
                shopify_json = sales_order.custom_shopify_order_json
            elif hasattr(sales_order, "custom_shopify_order_id") and sales_order.custom_shopify_order_id:
                # 如果没有完整 JSON，至少记录订单 ID
                shopify_json = json.dumps({"id": sales_order.custom_shopify_order_id}, ensure_ascii=False)
            
            api_traking.create_or_update_logistics_order_inst(
                sales_order_name=sales_order.name,
                waybill_number=waybill_number,
                tracking_number=tracking_number,
                product_code=product_code,
                receiver_info=receiver_info,
                shopify_order_json=shopify_json,
            )
        except Exception as e:
            frappe.logger().error(f"创建/更新 Logistics Order Inst 失败: {str(e)}")
            # 不影响主流程，继续执行

        return {
            "success": True,
            "message": "云途订单创建成功",
            "data": {
                "waybill_number": waybill_number,
                "tracking_number": tracking_number,
                "customer_order_number": customer_order_number,
                "label_url": label_url,
                "product_code": product_code
            }
        }

    except Exception as e:
        frappe.logger().error(f"创建云途订单失败: {str(e)}")
        return {
            "success": False,
            "message": f"创建云途订单失败: {str(e)}",
            "data": None
        }


def build_yunexpress_order_data(sales_order, receiver_info, sender_address, product_code):
    """
    构建云途订单数据
    """
    try:
        total_weight = 0.0
        declaration_info = []

        # 判断目的国家是否为美国（用于选择申报价值字段）
        is_us_order = False
        try:
            recv_country_code = ""
            if isinstance(receiver_info, dict):
                recv_country_code = (
                    str(receiver_info.get("country_code")
                        or receiver_info.get("country")
                        or "")
                    .strip()
                )
            code_upper = recv_country_code.upper()
            if code_upper in ("US", "USA", "UNITED STATES", "UNITED STATES OF AMERICA"):
                is_us_order = True
        except Exception:
            is_us_order = False

        for item in sales_order.items:

            if not item.item_code:
                continue

            try:
                item_doc = frappe.get_doc("Item", item.item_code)
            except frappe.DoesNotExistError:
                frappe.logger().warning(f"Item {item.item_code} not found when preparing Yunexpress data")
                continue

            qty = int(item.qty or 0)
            if qty <= 0:
                continue

            # FOB/申报价格（USD）：
            # - 若目的国家为美国，沿用原逻辑：取 Item.valuation_rate
            # - 若目的国家非美国，优先取 Item.custom_valuation_rate2（其他地区申报价）
            unit_price = 0.0
            try:
                if is_us_order:
                    price_val = getattr(item_doc, "valuation_rate", 0)
                    unit_price = float(price_val or 0)
                else:
                    price_val = getattr(item_doc, "custom_valuation_rate2", None)
                    if price_val not in (None, ""):
                        unit_price = float(price_val or 0)
                    else:
                        # 若未配置其他地区估值，则降级回退到 valuation_rate，避免 0 价报关
                        price_val = getattr(item_doc, "valuation_rate", 0)
                        unit_price = float(price_val or 0)
            except Exception:
                unit_price = 0.0
            # 单位重量（KG）：取 Item.weight_per_unit（你提供的字段）
            try:
                unit_weight = float(getattr(item_doc, "weight_per_unit", 0))
            except Exception:
                unit_weight = 0.0
            if unit_weight <= 0:
                # 防止云途接口校验失败，给出安全下限
                unit_weight = 0.05
            total_weight += unit_weight * qty

            # 中文名称：custom_item_name_cn；英文名仍取 item_name
            name_en = item.item_name 
            name_local = getattr(item_doc, "custom_item_name_cn", "") 

            # HS Code：custom_hs_code
            hs_code = getattr(item_doc, "custom_hs_code", "")   
            declaration_info.append(
                {
                    "sku_code": item.item_code,
                    "name_local": name_local,
                    "name_en": name_en,
                    "quantity": qty,
                    "unit_price": unit_price,
                    "unit_weight": unit_weight,
                    "hs_code": hs_code,
                    "sales_url": "sales_url",
                    "currency": "USD",
                    "material": "material",
                    "purpose": "purpose",
                    "brand": "brand",
                    "spec": "spec",
                    "model": "model",
                    "remark": "remark",
                }
            )

        if total_weight <= 0:
            total_weight = 0.05

        # 若只有一个包裹：包裹重量用汇总重量（避免固定值）
        safe_total_weight = total_weight if total_weight > 0 else 0.05
        # 仅传重量，长度/宽度/高度留空由云途默认处理
        packages = [
            {
                "weight": round(safe_total_weight, 3),
            }
        ]

        order_data = {
            "product_code": product_code,
            "customer_order_number": sales_order.name,
            "order_numbers": {
                "waybill_number": "",
                "platform_order_number": sales_order.name,
                "tracking_number": "",
                "reference_numbers": [],
            },
            "weight_unit": "KG",
            "size_unit": "CM",
            "dangerous_goods_type": "",  # 默认填空，避免误触渠道校验
            "packages": packages,
            "receiver": {
                "first_name": receiver_info.get("first_name") or receiver_info.get("name") or receiver_info.get("address_title") or "",
                "last_name": receiver_info.get("last_name") or "",
                "company": receiver_info.get("company") or receiver_info.get("address_title") or "",
                "country_code": receiver_info.get("country_code") or receiver_info.get("country"),
                "province": receiver_info.get("state") or receiver_info.get("province"),
                "city": receiver_info.get("city"),
                "address_lines": [part for part in (receiver_info.get("address_lines") or []) if part]
                or [receiver_info.get("address_line1") or ""],
                "postal_code": receiver_info.get("pincode") or receiver_info.get("postal_code"),
                "phone_number": receiver_info.get("phone") or "",
                "email": receiver_info.get("email_id") or receiver_info.get("email") or "",
                "certificate_type": "",
                "certificate_code": "",
            },
            "declaration_info": declaration_info,
            "sender": {
                "first_name": sender_address.address_title if sender_address else "Sender",
                "last_name": "",
                "company": sales_order.company or "",
                "country_code": sender_address.country_code if sender_address and getattr(sender_address, "country_code", None) else "CN",
                "province": sender_address.state if sender_address and sender_address.state else "",
                "city": sender_address.city if sender_address else "",
                "address_lines": [
                    line
                    for line in [
                        getattr(sender_address, "address_line1", "") if sender_address else "",
                        getattr(sender_address, "address_line2", "") if sender_address else "",
                    ]
                    if line
                ],
                "postal_code": sender_address.pincode if sender_address else "",
                "phone_number": sender_address.phone if sender_address else "",
                "email": sender_address.email_id if sender_address and sender_address.email_id else "",
                "certificate_type": "",
                "certificate_code": "",
            },
            "customs_number": {
                "tax_number": "",
                "ioss_code": "",
                "vat_code": "",
                "eori_number": "",
            },
            "extra_services": [],
            "platform_account_code": "",
            # 平台信息：平台名称必填，按照云途文档仅传 platform_name
            "platform": {
                "platform_name": "Flowa",
            },
            "source_code": "YT",
            "sensitive_type": "D",
            "label_type": "PDF",
            "point_relais_num": "",
        }

        return order_data

    except Exception as e:
        frappe.logger().error(f"构建云途订单数据失败: {str(e)}")
        raise e

def get_yunexpress_label(waybill_number):
    """
    获取云途面单
    """
    try:
        query_data = {
            "order_number": waybill_number
        }

        result = yt.get_order_label(query_data)

        if not result.get("success"):
            return {
                "success": False,
                "message": f"获取面单失败: {result.get('msg', '未知错误')}",
                "data": None
            }

        label_data = result.get("result", {})
        frappe.logger().error(
            f"云途面单获取成功: waybill={waybill_number}, url={label_data.get('url', '')}, label_type={label_data.get('label_type', 'PDF')}"
        )
        return {
            "success": True,
            "message": "获取面单成功",
            "data": {
                "url": label_data.get("url", ""),
                "label_type": label_data.get("label_type", "PDF")
            }
        }

    except Exception as e:
        frappe.logger().error(f"获取云途面单失败: {str(e)}")
        return {
            "success": False,
            "message": f"获取面单失败: {str(e)}",
            "data": None
        }

def save_yunexpress_info_to_sales_order(sales_order, yt_info):
    """
    保存云途信息到销售订单
    """
    try:
        comment_lines = [
            "OMS物流信息:",
            f"运单号: {yt_info.get('waybill_number', '')}",
            f"跟踪号: {yt_info.get('tracking_number', '')}",
            f"客户订单号: {yt_info.get('customer_order_number', '')}",
            f"面单URL: {yt_info.get('label_url', '')}",
            f"物流产品代码: {yt_info.get('product_code', '')}",
        ]
        comment_text = "\n".join(comment_lines)

        sales_order.add_comment("Info", comment_text)
        frappe.logger().info(f"已保存云途信息到销售订单 {sales_order.name}")

    except Exception as e:
        frappe.logger().error(f"保存云途信息到销售订单失败: {str(e)}")

# ========================================
# 旺店通WMS同步接口
# ========================================

@frappe.whitelist()
def sync_item_to_wdt(item_code):
    """
    同步单个商品到旺店通WMS
    """
    try:
        # 调试信息
        frappe.logger().info(f"sync_item_to_wdt called with item_code: '{item_code}'")

        # 检查参数
        if not item_code:
            frappe.logger().error("sync_item_to_wdt: item_code is empty")
            return {
                "success": False,
                "message": "商品编码不能为空",
                "data": None
            }

        # 清理和验证item_code
        item_code = str(item_code).strip()
        if not item_code or len(item_code) < 2:
            frappe.logger().error(f"sync_item_to_wdt: 无效的商品编码: '{item_code}'")
            return {
                "success": False,
                "message": f"无效的商品编码: {item_code}",
                "data": None
            }

        # 检查商品是否存在
        try:
            # 先查询商品是否存在
            items = frappe.get_all("Item", filters={"item_code": item_code}, fields=["name", "item_code"])

            if not items:
                frappe.logger().error(f"商品不存在: {item_code}")
                return {
                    "success": False,
                    "message": f"商品 {item_code} 不存在",
                    "data": None
                }

            item = frappe.get_doc("Item", item_code)
            frappe.logger().info(f"成功获取商品: {item.item_code}")
        except frappe.DoesNotExistError:
            frappe.logger().error(f"商品不存在: {item_code}")
            return {
                "success": False,
                "message": f"商品 {item_code} 不存在",
                "data": None
            }
        except Exception as e:
            frappe.logger().error(f"获取商品时发生错误: {str(e)}")
            return {
                "success": False,
                "message": f"获取商品失败: {str(e)}",
                "data": None
            }

        # 构建旺店通商品数据
        item_data = {
            "actionType": "add",  # add-新增，update-修改
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 默认仓库代码
            "item": {
                "itemCode": item.item_code,
                "itemName": item.item_name,
                "itemType": "ZC",  # ZC-正常商品，BC-包材
                "categoryCode": item.item_group or "DEFAULT",
                "brandCode": item.brand or "DEFAULT",
                "barCode": item.item_code,  # 条形码，使用商品编码
                "stockUnit": item.stock_uom or "PCS",
                "grossWeight": item.weight_per_unit or 0,
                "length": 0,  # ERPNext Item没有length字段
                "width": 0,   # ERPNext Item没有width字段
                "height": 0,  # ERPNext Item没有height字段
                "isSNMgmt": "Y" if item.has_serial_no else "N",
                "shelfLife": item.shelf_life_in_days or 0,
                "isBatchMgmt": "Y" if item.has_batch_no else "N",
                "remark": item.description or "",
                "extendProps": {
                    "imageUrl": item.image or "",  # 商品图片URL
                    "specprop1": getattr(item, 'custom_item_name_cn', '') or ""  # 商品中文名称
                }
            }
        }

        # 调用旺店通API
        frappe.logger().info(f"准备调用旺店通API，商品: {item_code}")
        result = wdt.wdt_wms_singleitem_synchronize(item_data)

        # 检查旺店通API响应格式
        frappe.logger().info(f"旺店通API响应: {result}")

        # 检查API调用是否成功
        if isinstance(result, dict):
            # 首先检查是否有success字段且为False（表示API调用失败）
            if result.get("success") == False:
                error_msg = result.get("message", "未知错误")
                frappe.logger().error(f"商品 {item_code} 同步到旺店通失败: {error_msg}")
                frappe.logger().error(f"旺店通API错误详情: {result}")
                return {
                    "success": False,
                    "message": f"商品 {item_code} 同步失败: {error_msg}",
                    "data": result
                }
            # 然后检查业务状态码（旺店通API成功响应格式：code="0"表示成功）
            elif result.get("code") == "0":
                frappe.logger().info(f"商品 {item_code} 同步到旺店通成功")
                return {
                    "success": True,
                    "message": f"商品 {item_code} 同步成功",
                    "data": result
                }
            # 其他情况，可能是业务错误
            else:
                error_msg = result.get("message", "未知错误")
                frappe.logger().error(f"商品 {item_code} 同步到旺店通失败: {error_msg}")
                frappe.logger().error(f"旺店通API完整响应: {result}")
                return {
                    "success": False,
                    "message": f"同步失败: {error_msg}",
                    "data": result
                }
        else:
            # 响应格式异常
            frappe.logger().error(f"商品 {item_code} 同步到旺店通失败，响应格式异常: {result}")
            return {
                "success": False,
                "message": f"同步失败，响应格式异常: {result}",
                "data": result
            }

    except Exception as e:
        frappe.logger().error(f"同步商品到旺店通失败: {str(e)}")
        frappe.logger().error(f"异常详情: {type(e).__name__}: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")
        return {
            "success": False,
            "message": f"同步失败: {str(e)}",
            "data": None
        }


@frappe.whitelist()
def batch_sync_items_to_wdt(item_codes):
    """
    批量同步商品到旺店通WMS
    """
    try:
        frappe.logger().info(f"batch_sync_items_to_wdt called with {len(item_codes) if isinstance(item_codes, list) else 'unknown'} items")

        # 处理参数 - 如果是字符串，需要解析为列表
        if isinstance(item_codes, str):
            import json
            try:
                item_codes = json.loads(item_codes)
                frappe.logger().info(f"解析后的item_codes: {item_codes}")
            except json.JSONDecodeError as e:
                frappe.logger().error(f"JSON解析失败: {e}")
                return {
                    "success": False,
                    "message": f"参数格式错误: {e}",
                    "data": None
                }

        if not isinstance(item_codes, list):
            return {
                "success": False,
                "message": "item_codes必须是列表格式",
                "data": None
            }

        success_count = 0
        fail_count = 0
        results = []

        for item_code in item_codes:
            frappe.logger().info(f"处理商品编码: '{item_code}'")
            try:
                result = sync_item_to_wdt(item_code)
                if result["success"]:
                    success_count += 1
                else:
                    fail_count += 1
                results.append({
                    "item_code": item_code,
                    "success": result["success"],
                    "message": result["message"]
                })
            except Exception as e:
                fail_count += 1
                results.append({
                    "item_code": item_code,
                    "success": False,
                    "message": str(e)
                })
                frappe.logger().error(f"批量同步商品 {item_code} 失败: {str(e)}")

        return {
            "success": True,
            "message": f"批量同步完成，成功: {success_count} 个，失败: {fail_count} 个",
            "data": {
                "success_count": success_count,
                "fail_count": fail_count,
                "results": results
            }
        }

    except Exception as e:
        frappe.logger().error(f"批量同步商品到旺店通失败: {str(e)}")
        return {
            "success": False,
            "message": f"批量同步失败: {str(e)}",
            "data": None
        }


# ========================================
# 销售订单同步到旺店通WMS接口
# ========================================

@frappe.whitelist()
def sync_sales_order_to_wdt(sales_order_name):
    """
    同步单个销售订单到旺店通WMS
    """
    try:
        # 调试信息
        frappe.logger().info(f"sync_sales_order_to_wdt called with sales_order_name: '{sales_order_name}'")

        # 检查参数
        if not sales_order_name:
            frappe.logger().error("sync_sales_order_to_wdt: sales_order_name is empty")
            return {
                "success": False,
                "message": "销售订单名称不能为空",
                "data": None
            }

        # 清理和验证sales_order_name
        sales_order_name = str(sales_order_name).strip()
        if not sales_order_name or len(sales_order_name) < 2:
            frappe.logger().error(f"sync_sales_order_to_wdt: 无效的销售订单名称: '{sales_order_name}'")
            return {
                "success": False,
                "message": f"无效的销售订单名称: {sales_order_name}",
                "data": None
            }

        # 检查销售订单是否存在
        try:
            # 先查询销售订单是否存在
            sales_orders = frappe.get_all("Sales Order", filters={"name": sales_order_name}, fields=["name", "docstatus", "status"])

            if not sales_orders:
                frappe.logger().error(f"销售订单不存在: {sales_order_name}")
                return {
                    "success": False,
                    "message": f"销售订单 {sales_order_name} 不存在",
                    "data": None
                }

            sales_order = frappe.get_doc("Sales Order", sales_order_name)
            frappe.logger().info(f"成功获取销售订单: {sales_order.name}")

            # 检查销售订单状态
            if sales_order.docstatus != 1:
                frappe.logger().error(f"销售订单未提交: {sales_order_name}")
                return {
                    "success": False,
                    "message": f"销售订单 {sales_order_name} 未提交，无法同步",
                    "data": None
                }

            if sales_order.status == "Closed":
                frappe.logger().error(f"销售订单已关闭: {sales_order_name}")
                return {
                    "success": False,
                    "message": f"销售订单 {sales_order_name} 已关闭，无法同步",
                    "data": None
                }

        except frappe.DoesNotExistError:
            frappe.logger().error(f"销售订单不存在: {sales_order_name}")
            return {
                "success": False,
                "message": f"销售订单 {sales_order_name} 不存在",
                "data": None
            }
        except Exception as e:
            frappe.logger().error(f"获取销售订单时发生错误: {str(e)}")
            return {
                "success": False,
                "message": f"获取销售订单失败: {str(e)}",
                "data": None
            }

        # 构建旺店通发货单数据
        # 获取仓库地址作为发货方（sender）
        warehouse = sales_order.set_warehouse or frappe.db.get_single_value("Stock Settings", "default_warehouse")
        if not warehouse:
            raise frappe.ValidationError("销售订单缺少关联仓库，无法创建云途运单")

        company_doc = frappe.get_cached_doc("Company", sales_order.company)
        prefer_private = bool(getattr(company_doc, "custom_is_virtual_trace", False))
        warehouse_address_doc = _get_warehouse_address_doc(warehouse, ["Shipping", "Warehouse", "Private"] if not prefer_private else ["Private", "Shipping", "Warehouse"])
        if not warehouse_address_doc:
            raise frappe.ValidationError(f"仓库 {warehouse} 没有关联的地址，无法创建云途运单")

        # 统一使用 Shopify 原始数据中的收货地址（若缺失则报错）
        try:
            from . import api_orders_import_shopify as shopify_mod
            receiver_info = shopify_mod._extract_shopify_shipping_address(sales_order)
        except Exception as e:
            raise frappe.ValidationError(f"Shopify 订单缺少收货地址，无法创建云途运单: {str(e)}")

        # 优先使用销售订单上的持久化字段（你已在数据库增加）
        try:
            selected_product_code = (getattr(sales_order, "custom_yt_product_code", None) or "").strip()
        except Exception:
            selected_product_code = ""
        if not selected_product_code:
            raise frappe.ValidationError("销售订单未配置物流产品代码(custom_yt_product_code)，无法创建云途运单")

        yt_result = create_yunexpress_order(
            sales_order,
            receiver_info,
            warehouse_address_doc,
            selected_product_code
        )
        if not yt_result.get("success"):
            raise frappe.ValidationError(f"云途订单创建失败: {yt_result.get('message')}")

        yt_data = yt_result.get("data", {})
        waybill_number = yt_data.get("waybill_number", "")
        tracking_number = yt_data.get("tracking_number", "")
        label_url = yt_data.get("label_url", "")

        if sales_order.meta.has_field("custom_yt_ret_data"):
            sales_order.db_set(
                "custom_yt_ret_data",
                json.dumps(yt_data, ensure_ascii=False),
                update_modified=False,
            )
        if sales_order.meta.has_field("custom_delivery_no") and tracking_number:
            sales_order.db_set("custom_delivery_no", tracking_number, update_modified=False)
        if sales_order.meta.has_field("custom_delivery_order_no") and waybill_number:
            sales_order.db_set(
                "custom_delivery_order_no", waybill_number, update_modified=False
            )
        if sales_order.meta.has_field("custom_label_url") and label_url:
            sales_order.db_set("custom_label_url", label_url, update_modified=False)

        delivery_data = build_wdt_delivery_data(
            sales_order,
            shipping_address=receiver_info,
            sender_address=warehouse_address_doc,
            waybill_number=waybill_number,
            tracking_number=tracking_number,
            label_url=label_url,
            logistics_product_code=selected_product_code,
        )

        # 调用旺店通API
        frappe.logger().debug(f"准备调用旺店通API，销售订单: {sales_order_name}")
        frappe.logger().debug(f"旺店通API调用数据 - delivery_data: {delivery_data}")
        frappe.logger().debug(f"旺店通API调用数据 - deliveryOrder: {delivery_data.get('deliveryOrder', {})}")
        frappe.logger().debug(f"旺店通API调用数据 - orderLines: {delivery_data.get('orderLines', [])}")
        frappe.logger().debug(f"旺店通API调用数据 - orderLines数量: {len(delivery_data.get('orderLines', []))}")

        # 打印每个订单行的详细信息
        for i, order_line in enumerate(delivery_data.get('orderLines', [])):
            frappe.logger().debug(f"旺店通API调用数据 - 第{i+1}行商品: {order_line}")

        result = wdt.wdt_wms_deliveryorder_create(delivery_data)

        # 检查旺店通API响应格式
        frappe.logger().info(f"旺店通API响应: {result}")

        # 检查API调用是否成功
        if isinstance(result, dict):
            if result.get("success") is False:
                error_msg = result.get("message", "未知错误")
                frappe.logger().error(f"销售订单 {sales_order_name} 同步到旺店通失败: {error_msg}")
                frappe.logger().error(f"旺店通API错误详情: {result}")
                return {
                    "success": False,
                    "message": error_msg,
                    "data": result,
                }

            if result.get("code") == "0":
                frappe.logger().info(f"销售订单 {sales_order_name} 同步到旺店通成功")
                # 成功后推送物流到 Shopify 履约（忽略失败但记录日志）
                try:
                    frappe.call(
                        "upsystem.upsystem.api_orders_import_shopify.push_shopify_tracking_from_yunexpress",
                        sales_order_name=sales_order_name,
                    )
                except Exception:
                    frappe.logger().error(
                        f"[Shopify Fulfillment] 旺店通同步后推送履约失败: {frappe.get_traceback()}"
                    )
                # 同步成功后，将销售订单置为“待出货”状态
                try:
                    so_doc = frappe.get_doc("Sales Order", sales_order_name)
                    if so_doc.status not in ["To Deliver", "Completed", "Closed", "Cancelled"]:
                        so_doc.status = "To Deliver"
                        so_doc.save(ignore_permissions=True)
                        frappe.db.commit()
                        frappe.logger().info(f"销售订单状态已更新为待出货: {sales_order_name}")
                except Exception:
                    frappe.logger().error(f"同步成功但更新状态为待出货失败: {frappe.get_traceback()}")

                # 成功后执行物流费用预扣逻辑（不影响主流程）
                reserve_info = None
                try:
                    from . import api_logistics_charging as charging_mod

                    reserve_info = charging_mod.reserve_logistics_fee_for_order(sales_order_name)
                except Exception:
                    frappe.logger().error(
                        f"[Logistics Reserve] 同步成功但预扣费逻辑执行失败: {frappe.get_traceback()}"
                    )

                return {
                    "success": True,
                    "message": f"销售订单 {sales_order_name} 同步成功",
                    "data": result,
                    "reserve_info": reserve_info,
                }

            error_msg = result.get("message", "未知错误")
            frappe.logger().error(f"销售订单 {sales_order_name} 同步到旺店通失败: {error_msg}")
            frappe.logger().error(f"旺店通API完整响应: {result}")
            return {
                "success": False,
                "message": error_msg,
                "data": result,
            }

        frappe.logger().error(f"销售订单 {sales_order_name} 同步到旺店通失败，响应格式异常: {result}")
        return {
            "success": False,
            "message": f"同步失败，响应格式异常: {result}",
            "data": result,
        }

    except Exception as e:
        frappe.logger().error(f"同步销售订单到旺店通失败: {str(e)}")
        frappe.logger().error(f"异常详情: {type(e).__name__}: {str(e)}")
        import traceback
        frappe.logger().error(f"异常堆栈: {traceback.format_exc()}")

        return {
            "success": False,
            "message": str(e),  # 直接返回原始错误信息
            "data": None
        }


@frappe.whitelist()
def batch_sync_sales_orders_to_wdt(sales_order_names):
    """
    批量同步销售订单到旺店通WMS
    """
    try:
        frappe.logger().info(f"batch_sync_sales_orders_to_wdt called with {len(sales_order_names) if isinstance(sales_order_names, list) else 'unknown'} orders")

        # 处理参数 - 如果是字符串，需要解析为列表
        if isinstance(sales_order_names, str):
            import json
            try:
                sales_order_names = json.loads(sales_order_names)
                frappe.logger().info(f"解析后的sales_order_names: {sales_order_names}")
            except json.JSONDecodeError as e:
                frappe.logger().error(f"JSON解析失败: {e}")
                return {
                    "success": False,
                    "message": f"参数格式错误: {e}",
                    "data": None
                }

        if not isinstance(sales_order_names, list):
            return {
                "success": False,
                "message": "sales_order_names必须是列表格式",
                "data": None
            }

        success_count = 0
        fail_count = 0
        results = []

        for sales_order_name in sales_order_names:
            frappe.logger().info(f"处理销售订单名称: '{sales_order_name}'")
            try:
                result = sync_sales_order_to_wdt(sales_order_name)
                if result["success"]:
                    success_count += 1
                else:
                    fail_count += 1
                results.append({
                    "sales_order_name": sales_order_name,
                    "success": result["success"],
                    "message": result["message"]
                })
            except Exception as e:
                fail_count += 1
                results.append({
                    "sales_order_name": sales_order_name,
                    "success": False,
                    "message": str(e)
                })
                frappe.logger().error(f"批量同步销售订单 {sales_order_name} 失败: {str(e)}")

        return {
            "success": True,
            "message": f"批量同步完成，成功: {success_count} 个，失败: {fail_count} 个",
            "data": {
                "success_count": success_count,
                "fail_count": fail_count,
                "results": results
            }
        }

    except Exception as e:
        frappe.logger().error(f"批量同步销售订单到旺店通失败: {str(e)}")
        return {
            "success": False,
            "message": f"批量同步失败: {str(e)}",
            "data": None
        }


def _get_warehouse_address_doc(warehouse_name: str, priority: List[str]):
    if not warehouse_name:
        return None

    try:
        warehouse_doc = frappe.get_cached_doc("Warehouse", warehouse_name)
    except frappe.DoesNotExistError:
        frappe.logger().error(f"仓库 {warehouse_name} 不存在，无法获取地址")
        return None

    # 通过 Dynamic Link 获取仓库关联的地址
    linked_names = frappe.get_all(
        "Dynamic Link",
        filters={
            "link_doctype": "Warehouse",
            "link_name": warehouse_name,
            "parenttype": "Address",
        },
        pluck="parent",
    )

    if not linked_names:
        return None

    for address_type in priority:
        matched = frappe.get_all(
            "Address",
            filters={
                "name": ["in", linked_names],
                "address_type": address_type,
            },
            order_by="is_primary_address desc, creation desc",
            pluck="name",
            limit=1,
        )
        if matched:
            return frappe.get_cached_doc("Address", matched[0])

    fallback = frappe.get_all(
        "Address",
        filters={"name": ["in", linked_names]},
        order_by="is_primary_address desc, creation desc",
        pluck="name",
        limit=1,
    )
    if fallback:
        return frappe.get_cached_doc("Address", fallback[0])

    return None


def _get_customer_shipping_address_doc(sales_order):
    """获取客户的收货地址"""
    shipping_address_name = getattr(sales_order, "shipping_address_name", None)
    if shipping_address_name:
        try:
            return frappe.get_doc("Address", shipping_address_name)
        except frappe.DoesNotExistError:
            raise frappe.ValidationError(
                f"客户收货地址不存在: {shipping_address_name}"
            )

    # 如果没有指定收货地址，尝试从客户获取默认地址
    customer = frappe.get_doc("Customer", sales_order.customer)
    customer_addresses = frappe.get_all(
        "Address",
        filters={
            "name": ["in", frappe.get_all(
                "Dynamic Link",
                filters={
                    "link_doctype": "Customer",
                    "link_name": sales_order.customer,
                    "parenttype": "Address",
                },
                pluck="parent",
            )]
        },
        fields=["name", "address_type", "is_primary_address"],
        order_by="is_primary_address desc, creation desc",
    )

    if customer_addresses:
        # 优先选择Shipping类型地址，其次选择Primary地址
        for address_type in ["Shipping", "Billing", "Other"]:
            for addr in customer_addresses:
                if addr.address_type == address_type:
                    return frappe.get_doc("Address", addr.name)

        # 如果没找到特定类型，返回第一个地址
        return frappe.get_doc("Address", customer_addresses[0].name)

    raise frappe.ValidationError(
        f"客户 {sales_order.customer} 没有配置收货地址，无法创建云途运单"
    )


def _get_sales_order_shipping_address_doc(sales_order):
    """获取销售订单的发货地址（从仓库获取）"""
    warehouse = sales_order.set_warehouse or frappe.db.get_single_value(
        "Stock Settings", "default_warehouse"
    )
    if not warehouse:
        raise frappe.ValidationError("销售订单缺少关联仓库，无法创建云途运单")

    shipping_address = _get_warehouse_address_doc(
        warehouse, ["Shipping", "Warehouse", "Private"]
    )
    if not shipping_address:
        raise frappe.ValidationError(
            frappe._("仓库 {0} 没有关联的地址，无法创建云途运单").format(
                warehouse
            )
        )

    return shipping_address


def _get_sales_order_sender_address_doc(sales_order, prefer_private: bool):
    """获取销售订单的发货地址（从仓库获取）"""
    warehouse = sales_order.set_warehouse or frappe.db.get_single_value(
        "Stock Settings", "default_warehouse"
    )
    if not warehouse:
        raise frappe.ValidationError("销售订单缺少关联仓库，无法创建云途运单")

    priority = ["Private", "Shipping", "Warehouse"] if prefer_private else [
        "Shipping",
        "Warehouse",
        "Private",
    ]
    sender_address = _get_warehouse_address_doc(warehouse, priority)
    if not sender_address:
        raise frappe.ValidationError(
            frappe._("仓库 {0} 没有关联的地址，无法创建云途运单").format(warehouse)
        )

    return sender_address


def _build_yunexpress_receiver_info_from_address(sales_order, shipping_address):
    if not shipping_address:
        raise frappe.ValidationError("销售订单缺少收货地址，无法创建云途运单")

    address_line1 = (
        (getattr(shipping_address, "address_line1", None) or "").strip()
        or (getattr(shipping_address, "address_line_1", None) or "").strip()
    )
    address_line2 = (
        (getattr(shipping_address, "address_line2", None) or "").strip()
        or (getattr(shipping_address, "address_line_2", None) or "").strip()
    )
    city = (getattr(shipping_address, "city", None) or "").strip()
    postal_code = (getattr(shipping_address, "pincode", None) or "").strip()
    country = getattr(shipping_address, "country", None)
    country_code = getattr(shipping_address, "country_code", None)

    if not address_line1 or not city or not postal_code or not (country or country_code):
        raise frappe.ValidationError("收货地址缺少必填字段，无法创建云途运单")

    if not country_code and country:
        country_code = frappe.db.get_value("Country", country, "code")

    first_name = (getattr(shipping_address, "first_name", None) or "").strip()
    last_name = (getattr(shipping_address, "last_name", None) or "").strip()

    if not first_name and not last_name:
        display_name = (
            getattr(shipping_address, "address_title", None)
            or getattr(shipping_address, "name", None)
            or sales_order.customer_name
            or sales_order.customer
            or ""
        )
        parts = display_name.split()
        if parts:
            first_name = parts[0]
            if len(parts) > 1:
                last_name = " ".join(parts[1:])

    company_name = (
        getattr(shipping_address, "address_title", None)
        or sales_order.customer_name
        or sales_order.customer
        or ""
    )

    phone_number = (
        getattr(shipping_address, "phone", None)
        or getattr(shipping_address, "phone_r", None)
        or getattr(shipping_address, "mobile", None)
        or getattr(shipping_address, "mobile_no", None)
        or ""
    )
    email = (
        getattr(shipping_address, "email_id", None)
        or getattr(sales_order, "contact_email", None)
        or ""
    )

    province = getattr(shipping_address, "state", None)

    address_lines = [line for line in [address_line1, address_line2] if line]

    return {
        "first_name": first_name or company_name,
        "last_name": last_name,
        "company": company_name,
        "country_code": country_code,
        "province": province or "",
        "city": city,
        "address_lines": address_lines or [address_line1],
        "postal_code": postal_code,
        "phone_number": phone_number,
        "email": email,
        "certificate_type": "",
        "certificate_code": "",
    }


def build_wdt_delivery_data(
    sales_order,
    *,
    shipping_address=None,
    sender_address=None,
    waybill_number: str = "",
    tracking_number: str = "",
    label_url: str = "",
    logistics_product_code: str = "",
):
    """
    构建旺店通发货单数据
    """
    try:
        from frappe.utils import get_datetime, format_datetime

        # 获取客户信息
        customer = frappe.get_doc("Customer", sales_order.customer)

        # 对于普通销售订单：shipping_address是客户收货地址，sender_address是仓库发货地址
        shipping_address = shipping_address or _get_customer_shipping_address_doc(sales_order)
        sender_address = sender_address or _get_sales_order_shipping_address_doc(sales_order)

        receiver_name = (
            getattr(shipping_address, "address_title", None)
            or getattr(shipping_address, "first_name", None)
            or customer.customer_name
            or sales_order.customer
            or ""
        )

        detail_address = " ".join(
            part
            for part in [
                (getattr(shipping_address, "address_line1", None) or "").strip(),
                (getattr(shipping_address, "address_line2", None) or "").strip(),
            ]
            if part
        )

        country_code = (
            getattr(shipping_address, "country_code", None)
            or (
                frappe.db.get_value("Country", getattr(shipping_address, "country", None), "code")
                if getattr(shipping_address, "country", None)
                else ""
            )
        )

        phone_value = (
            getattr(shipping_address, "phone", None)
            or getattr(shipping_address, "phone_r", None)
            or getattr(shipping_address, "mobile", None)
            or getattr(shipping_address, "mobile_no", None)
            or ""
        )

        delivery_order = {
            "deliveryOrderCode": sales_order.name,  # 出库单号
            "preDeliveryOrderCode": sales_order.name,  # 原出库单号
            "orderType": "JYCK",  # 出库单类型：一般交易出库单
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 仓库编码
            "sourcePlatformCode": "ZYPT",  # 订单来源平台：自有平台
            "sourcePlatformName": "ERPNext",
            "createTime": format_datetime(sales_order.creation, "YYYY-MM-DD HH:mm:ss"),
            "placeOrderTime": format_datetime(sales_order.transaction_date, "YYYY-MM-DD HH:mm:ss"),
            "operateTime": format_datetime(sales_order.modified, "YYYY-MM-DD HH:mm:ss"),
            "shopNick": "ERPNext店铺",
            "buyerNick": customer.customer_name or sales_order.customer,
            "totalAmount": float(sales_order.grand_total or 0),
            "itemAmount": float(sales_order.net_total or 0),
            "discountAmount": float(sales_order.discount_amount or 0),
            "freight": 0.0,  # 运费由旺店通系统计算
            "arAmount": float(sales_order.grand_total or 0),
            "gotAmount": float(sales_order.advance_paid or 0),
            "logisticsCode": "YUNEXPRESS",  # 云途物流公司编码
            "logisticsName": "云途物流",
            "expressCode": waybill_number or "",  # 云途运单号
            "logisticsAreaCode": "",  # 快递区域编码
            "invoiceFlag": "N",  # 是否需要发票，N=不需要
            "buyerMessage": sales_order.terms or "",
            "sellerMessage": "",
            "printRemark": "",
            "remark": f"ERPNext销售订单: {sales_order.name}",
            "extendProps": {
                "foreignTrade": "Y",  # 是否为跨境订单，推送旺店通要求为Y
                "expressPDF": label_url or "",  # 云途面单PDF URL
                "logistics_flag": "YUNEXPRESS",  # 物流标志
                "tracking_number": tracking_number or "",  # 跟踪号
                "product_code": logistics_product_code or "",  # 物流产品代码
            },
        }
        delivery_order = {
            "deliveryOrderCode": sales_order.name,  # 出库单号
            "preDeliveryOrderCode": sales_order.name,  # 原出库单号
            "orderType": "JYCK",  # 出库单类型：一般交易出库单
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 仓库编码
            "sourcePlatformCode": "ZYPT",  # 订单来源平台：自有平台
            "sourcePlatformName": "ERPNext",
            "createTime": format_datetime(sales_order.creation, "YYYY-MM-DD HH:mm:ss"),
            "placeOrderTime": format_datetime(sales_order.transaction_date, "YYYY-MM-DD HH:mm:ss"),
            "operateTime": format_datetime(sales_order.modified, "YYYY-MM-DD HH:mm:ss"),
            "shopNick": "ERPNext店铺",
            "buyerNick": customer.customer_name or sales_order.customer,
            "totalAmount": float(sales_order.grand_total or 0),
            "itemAmount": float(sales_order.net_total or 0),
            "discountAmount": float(sales_order.discount_amount or 0),
            "freight": 0.0,  # 运费由旺店通系统计算
            "arAmount": float(sales_order.grand_total or 0),
            "gotAmount": float(sales_order.advance_paid or 0),
            "logisticsCode": "YUNEXPRESS",  # 云途物流公司编码
            "logisticsName": "云途物流",
            "expressCode": waybill_number,  # 云途运单号
            "logisticsAreaCode": "",  # 快递区域编码
            "invoiceFlag": "N",  # 是否需要发票，N=不需要
            "buyerMessage": sales_order.terms or "",
            "sellerMessage": "",
            "printRemark": "",
            "remark": f"ERPNext销售订单: {sales_order.name}",
            "extendProps": {
                "foreignTrade": "Y",  # 是否为跨境订单，推送旺店通要求为Y
                "expressPDF": label_url,  # 云途面单PDF URL
                "logistics_flag": "YUNEXPRESS",  # 物流标志
                "tracking_number": tracking_number,  # 跟踪号
                "product_code": logistics_product_code  # 物流产品代码
            }
        }

        # 添加发货方信息（使用仓库地址）
        delivery_order["senderInfo"] = {
            "company": sales_order.company or "",
            "name": getattr(sender_address, "address_title", ""),
            "zipCode": getattr(sender_address, "pincode", ""),
            "tel": getattr(sender_address, "phone", ""),
            "mobile": getattr(sender_address, "phone", ""),
            "email": getattr(sender_address, "email_id", ""),
            "countryCode": getattr(sender_address, "country_code", "")
            or getattr(sender_address, "country", ""),
            "province": getattr(sender_address, "state", ""),
            "city": getattr(sender_address, "city", ""),
            "area": getattr(sender_address, "county", ""),
            "detailAddress": (
                getattr(sender_address, "address_line1", "")
                or getattr(sender_address, "address_line_1", "")
            ),
        }

        # 添加收货方信息
        delivery_order["receiverInfo"] = {
            "name": receiver_name,
            "zipCode": getattr(shipping_address, "pincode", ""),
            "tel": phone_value,
            "mobile": phone_value,
            "idType": 1,
            "idNumber": "",
            "countryCode": country_code,
            "province": getattr(shipping_address, "state", ""),
            "city": getattr(shipping_address, "city", ""),
            "area": getattr(shipping_address, "county", ""),
            "town": "",
            "detailAddress": detail_address,
            "oaid": "",
        }

        # 检查销售订单是否有商品行项目
        if not sales_order.items:
            frappe.logger().error(f"销售订单 {sales_order.name} 没有商品行项目")
            raise frappe.ValidationError("销售订单没有商品行项目，无法同步到旺店通")

        frappe.logger().debug(f"销售订单 {sales_order.name} 共有 {len(sales_order.items)} 个商品行项目")

        # 构建订单行项目
        order_lines = []
        for idx, item in enumerate(sales_order.items):
            # 验证商品编码
            if not item.item_code:
                frappe.logger().error(f"销售订单 {sales_order.name} 第 {idx + 1} 行商品缺少 item_code")
                raise frappe.ValidationError(f"销售订单第 {idx + 1} 行商品缺少商品编码")

            # 调试日志
            frappe.logger().debug(f"处理商品: item_code={item.item_code}, item_name={item.item_name}, qty={item.qty}")

            order_line = {
                "orderLineNo": str(idx + 1),
                "sourceOrderCode": sales_order.name,
                "subSourceOrderCode": "",  # 交易平台子订单编码
                "itemCode": str(item.item_code).strip(),  # 确保是字符串且去除空格
                "itemName": item.item_name or item.description or "",
                "inventoryType": "ZP",  # 正品
                "planQty": float(item.qty or 0),
                "retailPrice": float(item.rate or 0),
                "actualPrice": float(item.rate or 0),
                "discountAmount": float(item.discount_amount or 0),
                "batchCode": "",  # 批次编码
                "remark": item.description or "",
                "extendProps": {
                    "isGift": 0  # 是否赠品，0=非赠品
                }
            }
            order_lines.append(order_line)

        delivery_data = {
            "deliveryOrder": delivery_order,
            "orderLines": order_lines
        }

        # 详细调试日志
        frappe.logger().info(f"构建的旺店通发货单数据: {delivery_data}")
        frappe.logger().info(f"订单行项目数量: {len(order_lines)}")
        for i, line in enumerate(order_lines):
            frappe.logger().info(f"第 {i+1} 行商品: itemCode={line.get('itemCode')}, itemName={line.get('itemName')}, planQty={line.get('planQty')}")

        return delivery_data

    except Exception as e:
        frappe.logger().error(f"构建旺店通发货单数据失败: {str(e)}")
        raise e


# ========================================
# 物料申请单同步到旺店通WMS接口
# ========================================

@frappe.whitelist()
def sync_material_request_to_wdt(material_request_name):
    """
    同步单个物料申请单到旺店通WMS
    """
    try:
        frappe.logger().error(f"sync_material_request_to_wdt called with material_request_name: '{material_request_name}'")

        # 检查参数
        if not material_request_name:
            frappe.logger().error("[MR Sync] param empty: material_request_name is empty")
            return {
                "success": False,
                "message": "物料申请单名称不能为空",
                "data": None
            }

        # 清理和验证material_request_name
        material_request_name = str(material_request_name).strip()
        if not material_request_name or len(material_request_name) < 2:
            frappe.logger().error(f"[MR Sync] invalid name | material_request_name={material_request_name}")
            return {
                "success": False,
                "message": f"无效的物料申请单名称: {material_request_name}",
                "data": None
            }

        # 检查物料申请单是否存在
        try:
            # 先查询物料申请单是否存在
            material_requests = frappe.get_all("Material Request", filters={"name": material_request_name}, fields=["name", "docstatus", "status"])
            if not material_requests:
                frappe.logger().error(f"[MR Sync] not found | material_request_name={material_request_name}")
                return {
                    "success": False,
                    "message": f"物料申请单不存在: {material_request_name}",
                    "data": None
                }

            # 获取完整的物料申请单数据
            material_request = frappe.get_doc("Material Request", material_request_name)
            frappe.logger().error(f"获取到物料申请单: {material_request.name}, 状态: {material_request.status}, 文档状态: {material_request.docstatus}")

        except Exception as e:
            frappe.logger().error(f"[MR Sync] load doc failed | material_request_name={material_request_name}, error={str(e)}")
            return {
                "success": False,
                "message": f"获取物料申请单失败: {str(e)}",
                "data": None
            }

        # 检查物料申请单状态
        if material_request.docstatus != 1:
            return {
                "success": False,
                "message": "只有已提交的物料申请单才能同步到旺店通",
                "data": None
            }

        if material_request.status == "Completed":
            return {
                "success": False,
                "message": "已完成的物料申请单不能同步到旺店通",
                "data": None
            }

        # 检查是否有商品行项目
        if not material_request.items or len(material_request.items) == 0:
            return {
                "success": False,
                "message": "物料申请单没有商品行项目，无法同步到旺店通",
                "data": None
            }

        # 构建旺店通入库单数据
        try:
            entryorder_data = build_wdt_entryorder_data_from_material_request(material_request)
        except Exception as e:
            frappe.logger().error(f"[MR Sync] build payload failed | name={material_request.name}, error={str(e)}")
            return {
                "success": False,
                "message": f"构建入库单数据失败: {str(e)}",
                "data": None
            }

        # 调用旺店通API
        try:
            frappe.logger().error(f"准备调用旺店通API，物料申请单: {material_request_name}")
            result = wdt.wdt_wms_entryorder_create(entryorder_data)
            frappe.logger().error(f"旺店通API响应: {result}")

            # 检查API调用是否成功
            if result and result.get("flag") == "success":
                frappe.logger().info(f"物料申请单 {material_request_name} 同步到旺店通成功")
                return {
                    "success": True,
                    "message": "物料申请单同步到旺店通成功",
                    "data": {
                        "material_request_name": material_request_name,
                        "wdt_entry_order_id": result.get("entryOrderId") or result.get("entryOrderld"),
                        "wdt_message": result.get("message")
                    }
                }
            else:
                error_msg = result.get("message", "未知错误") if result else "API调用失败"
                frappe.logger().error(f"[MR Sync] WDT failed | name={material_request_name}, error={error_msg}, response={result}")
                return {
                    "success": False,
                    "message": f"旺店通API调用失败: {error_msg}",
                    "data": None
                }

        except Exception as e:
            frappe.logger().error(f"[MR Sync] call WDT exception | name={material_request_name}, error={str(e)}")
            return {
                "success": False,
                "message": f"调用旺店通API失败: {str(e)}",
                "data": None
            }

    except Exception as e:
        frappe.logger().error(f"[MR Sync] top-level exception | name={material_request_name}, error={str(e)}")
        return {
            "success": False,
            "message": f"同步失败: {str(e)}",
            "data": None
        }


@frappe.whitelist()
def batch_sync_material_requests_to_wdt(material_request_names):
    """
    批量同步物料申请单到旺店通WMS
    """
    try:
        # 检查参数
        if not material_request_names or not isinstance(material_request_names, list):
            return {
                "success": False,
                "message": "物料申请单名称列表不能为空",
                "data": None
            }

        if len(material_request_names) == 0:
            return {
                "success": False,
                "message": "请选择要同步的物料申请单",
                "data": None
            }

        success_count = 0
        fail_count = 0
        results = []

        for material_request_name in material_request_names:
            frappe.logger().error(f"[MR Sync][Batch] item | name={material_request_name}")
            try:
                result = sync_material_request_to_wdt(material_request_name)
                if result["success"]:
                    success_count += 1
                else:
                    fail_count += 1
                results.append({
                    "material_request_name": material_request_name,
                    "success": result["success"],
                    "message": result["message"]
                })
            except Exception as e:
                fail_count += 1
                results.append({
                    "material_request_name": material_request_name,
                    "success": False,
                    "message": str(e)
                })
                frappe.logger().error(f"[MR Sync][Batch] item failed | name={material_request_name}, error={str(e)}")

        return {
            "success": True,
            "message": f"批量同步完成，成功: {success_count} 个，失败: {fail_count} 个",
            "data": {
                "success_count": success_count,
                "fail_count": fail_count,
                "results": results
            }
        }

    except Exception as e:
        frappe.logger().error(f"[MR Sync][Batch] top-level exception | error={str(e)}")
        return {
            "success": False,
            "message": f"批量同步失败: {str(e)}",
            "data": None
        }


def build_wdt_entryorder_data_from_material_request(material_request):
    """
    构建旺店通入库单数据（从物料申请单）
    根据旺店通接口文档 WDT_WMS_ENTRYORDER_CREATE 规范
    """
    try:
        from frappe.utils import get_datetime, format_datetime

        # 默认发货人信息（使用仓库信息作为兜底，防止 sender_info 未定义）
        sender_info = {
            "name": "发货人",
            "tel": "",
            "mobil": "",
            "detailAddress": ""
        }
        
        # 如果 Material Request 配置了发货地址，则以该地址覆盖默认值
        if hasattr(material_request, 'custom_address') and material_request.custom_address:
            try:
                sender_doc = frappe.get_doc("Address", material_request.custom_address)
                sender_info = {
                    "name": sender_doc.address_title or sender_doc.name,
                    "tel": sender_doc.phone or "",
                    "mobil": sender_doc.mobile_no or "",
                    "detailAddress": f"{sender_doc.address_line1 or ''} {sender_doc.address_line2 or ''} {sender_doc.city or ''} {sender_doc.state or ''} {sender_doc.pincode or ''}".strip()
                }
            except Exception as e:
                frappe.logger().warning(f"获取发货人信息失败: {e}，使用默认发货人信息")
        
        # 构建入库单基本信息
        entry_order = {
            "entryOrderCode": material_request.name,  # 入库单号
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 使用仓库编码
            "orderType": "CGRK",  # 入库类型：采购入库
            "supplierCode": "DEFAULT_SUPPLIER",  # 物料申请单没有供应商字段
            "supplierName": "默认供应商",
            "logisticsName": getattr(material_request, 'custom_logisticsname', '') or "",  # 物流公司
            "expressCode": getattr(material_request, 'custom_expresscode', '') or "",  # 物流单号
            "remark": material_request.terms or "",  # 单据备注
            "senderInfo": sender_info,
            "extendProps": {
                "purchaserName": material_request.owner,  # 申请人
                "receipt_type": "物料申请入库",
                "supplierMsg": f"物料申请单: {material_request.name}",
                "material_request_code": material_request.name,  # 物料申请单号，用于回调时识别
                "prop1": material_request.name,  # 自定义属性1：物料申请单号
                "prop2": "MATERIAL_REQUEST"  # 自定义属性2：标识这是物料申请单
            }
        }

        # 构建商品明细
        order_lines = []
        for idx, item in enumerate(material_request.items):
            if not item.item_code:
                frappe.logger().error(f"物料申请单 {material_request.name} 第 {idx + 1} 行商品缺少 item_code")
                continue

            # 获取商品信息
            item_doc = frappe.get_doc("Item", item.item_code) if item.item_code else None

            # 处理生产日期、批次信息和过期日期
            product_date = ""
            expire_date = ""
            batch_code = ""

            # 生产日期
            if hasattr(item, 'custom_manufacture_date') and item.custom_manufacture_date:
                product_date = str(item.custom_manufacture_date)

            # 生产批次号
            if hasattr(item, 'custom_manufacture_batch_no') and item.custom_manufacture_batch_no:
                batch_code = str(item.custom_manufacture_batch_no).strip()

            # 过期日期 - 直接使用自定义字段
            if hasattr(item, 'custom_expire_date') and item.custom_expire_date:
                expire_date = str(item.custom_expire_date)

            order_line = {
                "orderLineNo": str(idx + 1),  # 单据行号
                "itemCode": str(item.item_code).strip(),  # 商家编码
                "itemName": item.item_name or item.description or "",  # 货品名称
                "planQty": float(item.qty or 0),  # 开单数量
                "purchasePrice": float(item.rate or 0),  # 采购单价
                "inventoryType": "ZP",  # 库存类型：正品
                "productDate": product_date,  # 生产日期
                "expireDate": expire_date,  # 有效期
                "batchCode": batch_code,  # 批次
                "remark": item.description or "",  # 货品备注
                "extendProps": {
                    "material_request_line": str(idx + 1)
                }
            }

            # 如果有生产日期和有效期信息，添加到商品明细中（覆盖之前的空值）
            if hasattr(item, 'manufacturing_date') and item.manufacturing_date:
                order_line["productDate"] = format_datetime(item.manufacturing_date, "YYYY-MM-DD HH:mm:ss")

            if hasattr(item, 'expiry_date') and item.expiry_date:
                order_line["expireDate"] = format_datetime(item.expiry_date, "YYYY-MM-DD HH:mm:ss")

            order_lines.append(order_line)

        if not order_lines:
            raise frappe.ValidationError("物料申请单没有有效的商品行项目，无法同步到旺店通")

        # 构建完整的入库单数据
        entryorder_data = {
            "entryOrder": entry_order,
            "orderLines": order_lines
        }

        # 详细调试日志
        frappe.logger().info(f"构建的旺店通入库单数据: {entryorder_data}")
        frappe.logger().info(f"入库单行项目数量: {len(order_lines)}")
        for i, line in enumerate(order_lines):
            frappe.logger().info(f"第 {i+1} 行商品: itemCode={line.get('itemCode')}, itemName={line.get('itemName')}, planQty={line.get('planQty')}")

        return entryorder_data

    except Exception as e:
        frappe.logger().error(f"构建旺店通入库单数据失败: {str(e)}")
        raise e

def build_wdt_entryorder_data(purchase_order):
    """
    构建旺店通入库单数据
    根据旺店通接口文档 WDT_WMS_ENTRYORDER_CREATE 规范
    """
    try:
        from frappe.utils import get_datetime, format_datetime

        # 获取供应商信息
        supplier = frappe.get_doc("Supplier", purchase_order.supplier) if purchase_order.supplier else None

        # 获取目标仓库信息
        if not purchase_order.set_warehouse:
            raise frappe.ValidationError("采购订单没有设置目标仓库，无法同步到旺店通")

        try:
            # 获取目标仓库信息
            warehouse = frappe.get_doc("Warehouse", purchase_order.set_warehouse)

            # 检查仓库是否有地址信息
            if not warehouse.address_line_1 and not warehouse.city:
                raise frappe.ValidationError(f"仓库 {purchase_order.set_warehouse} 没有设置地址信息，无法同步到旺店通")

        except frappe.DoesNotExistError:
            raise frappe.ValidationError(f"仓库 {purchase_order.set_warehouse} 不存在，无法同步到旺店通")
        except Exception as e:
            raise frappe.ValidationError(f"获取仓库信息失败: {str(e)}")

        # 构建入库单基本信息
        entry_order = {
            "entryOrderCode": purchase_order.name,  # 入库单号
            "warehouseCode": WDT_WAREHOUSE_CODE,  # 使用仓库编码
            "orderType": "CGRK",  # 入库类型：采购入库
            "supplierCode": supplier.supplier_code if supplier and hasattr(supplier, 'supplier_code') else purchase_order.supplier,
            "supplierName": supplier.supplier_name if supplier else purchase_order.supplier,
            "logisticsName": "",  # 物流公司
            "expressCode": "",  # 物流单号
            "remark": purchase_order.notes or "",  # 单据备注
            "senderInfo": {
                "name": "发货人",  # 默认发货人名称
                "tel": warehouse.phone_no or "",
                "mobil": warehouse.mobile_no or "",
                "detailAddress": f"{warehouse.address_line_1 or ''} {warehouse.address_line_2 or ''}".strip()
            },
            "extendProps": {
                "purchaserName": purchase_order.owner,  # 采购员
                "receipt_type": "采购入库",
                "supplierMsg": f"供应商: {purchase_order.supplier}"
            }
        }

        # 构建商品明细
        order_lines = []
        for idx, item in enumerate(purchase_order.items):
            if not item.item_code:
                frappe.logger().error(f"采购订单 {purchase_order.name} 第 {idx + 1} 行商品缺少 item_code")
                continue

            # 获取商品信息
            item_doc = frappe.get_doc("Item", item.item_code) if item.item_code else None

            order_line = {
                "orderLineNo": str(idx + 1),  # 单据行号
                "itemCode": str(item.item_code).strip(),  # 商家编码
                "itemName": item.item_name or item.description or "",  # 货品名称
                "planQty": float(item.qty or 0),  # 开单数量
                "purchasePrice": float(item.rate or 0),  # 采购单价
                "inventoryType": "ZP",  # 库存类型：正品
                "productDate": "",  # 生产日期
                "expireDate": "",  # 有效期
                "batchCode": "",  # 批次
                "remark": item.description or "",  # 货品备注
                "extendProps": {
                    "purchase_order_line": str(idx + 1)
                }
            }

            # 如果有生产日期和有效期信息，添加到商品明细中
            if hasattr(item, 'manufacturing_date') and item.manufacturing_date:
                order_line["productDate"] = format_datetime(item.manufacturing_date, "YYYY-MM-DD HH:mm:ss")

            if hasattr(item, 'expiry_date') and item.expiry_date:
                order_line["expireDate"] = format_datetime(item.expiry_date, "YYYY-MM-DD HH:mm:ss")

            order_lines.append(order_line)

        if not order_lines:
            raise frappe.ValidationError("采购订单没有有效的商品行项目，无法同步到旺店通")

        # 构建完整的入库单数据
        entryorder_data = {
            "entryOrder": entry_order,
            "orderLines": order_lines
        }

        # 详细调试日志
        frappe.logger().info(f"构建的旺店通入库单数据: {entryorder_data}")
        frappe.logger().info(f"入库单行项目数量: {len(order_lines)}")
        for i, line in enumerate(order_lines):
            frappe.logger().info(f"第 {i+1} 行商品: itemCode={line.get('itemCode')}, itemName={line.get('itemName')}, planQty={line.get('planQty')}")

        return entryorder_data

    except Exception as e:
        frappe.logger().error(f"构建旺店通入库单数据失败: {str(e)}")
        raise e

@frappe.whitelist()
def cancel_material_request_in_wdt(material_request_name, cancel_reason):
    """
    取消物料申请单在旺店通WMS中的对应单据（入库单）

    Args:
        material_request_name (str): 物料申请单名称
        cancel_reason (str): 取消原因（必填）

    Returns:
        dict: 操作结果
    """
    try:
        # 验证取消原因
        if not cancel_reason or not str(cancel_reason).strip():
            return {"success": False, "message": "取消原因不能为空"}

        # 加载物料申请单
        material_request = frappe.get_doc("Material Request", material_request_name)
        if material_request.docstatus != 1:
            return {"success": False, "message": "只有已提交的物料申请单可以取消"}
        if getattr(material_request, "status", "") == "Cancelled":
            return {"success": False, "message": "物料申请单已取消，无需重复取消"}

        # 构建取消数据（物料申请单在WDT侧为入库单：CGRK）
        cancel_data = {
            "warehouseCode": WDT_WAREHOUSE_CODE,
            "orderCode": material_request_name,
            "orderType": "CGRK",
            "cancelReason": cancel_reason,
        }

        # 调用旺店通取消接口
        result = wdt.wdt_wms_order_cancel(cancel_data)

        if result and result.get("code") == "0":
            # 成功：记录原因并取消ERP单据
            cancel_comment = f"订单取消原因: {cancel_reason}"
            material_request.add_comment("Comment", cancel_comment)
            material_request.cancel()
            return {
                "success": True,
                "message": "物料申请单取消成功",
                "data": {
                    "wdt_result": result,
                },
            }
        else:
            # 若WDT提示单据不存在，则视为未同步到WDT，仍允许本地取消
            wdt_message = result.get("message", "接口调用失败") if result else "接口调用失败"
            wdt_code = result.get("code", "unknown") if result else "no_response"
            wdt_flag = result.get("flag", "unknown") if result else "no_response"

            if "不存在" in wdt_message:
                cancel_comment = f"订单取消原因: {cancel_reason}；OMS订单不存在（未同步）"
                material_request.add_comment("Comment", cancel_comment)
                material_request.cancel()
                return {
                    "success": True,
                    "message": "物料申请单本地取消成功（WDT不存在对应单据）",
                    "data": {
                        "wdt_result": result,
                        "wdt_code": wdt_code,
                        "wdt_flag": wdt_flag,
                        "wdt_message": wdt_message,
                    },
                }

            # 其他错误：返回失败详情
            return {
                "success": False,
                "message": f"OMS取消失败: {wdt_message}",
                "wdt_code": wdt_code,
                "wdt_flag": wdt_flag,
                "wdt_message": wdt_message,
                "data": {"wdt_result": result} if result else None,
            }

    except Exception as e:
        frappe.logger().error(f"[MR Cancel] exception | name={material_request_name}, error={str(e)}")
        return {"success": False, "message": f"取消失败: {str(e)}"}
